/*
 * Copyright (C) 2008 Most Publishing
 * Copyright (C) 2008 Dmitry Zakharov <dmitry-z@mail.ru>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include <ZLibrary.h>
#include <ZLApplication.h>
#include "../../../../../fbreader/src/fbreader/FBReader.h"
#include "../../../../../fbreader/src/fbreader/BookTextView.h"
#include "../../../../../fbreader/src/fbreader/FootnoteView.h"
#include "../../../../../fbreader/src/fbreader/FBReaderActions.h"
#include "../../../../../fbreader/src/formats/FormatPlugin.h"
#include "../../../../../fbreader/src/bookmodel/BookModel.h"
#include "../view/ZLNXViewWidget.h"
//#include "../../../../core/include/ZLFile.h"
#include "../filesystem/ZLNXFSManager.h"
#include "../../../../core/include/ZLXMLWriter.h"
#include "../../../../text/include/ZLTextStats.h"

#include <stdio.h>
#include <stdlib.h>
#include <inkview.h>
#include <inkinternal.h>

#include <list>

#include "main.h"

#include "statsfile.h"

#define	MODDER_VERSION	"Modified by SciFiFan, JAW, GrayNM, Antuanelli\nVersion 0.11.3"

// enable this definition to make stats build
//#define ENABLE_STATS_HARVESTING

/** GrayNM: Localization
 * 1. Localization resources and holder class were taken from FBReader.
 *    They are located in /mnt/ext1/system/share/FBReader/resources.
 * 2. Only strings started from '@' were translated.
 * 3. Configuration structure strings must be placed in resource hierarchically starting at tag "config".
 * 4. Configuration resources are named according to menu item names, i.e. field "name" in iconfigedit.
 * 5. Configuration item variant values were translated by its text.
 * 6. Styles edit submenu items were translated via tag "style" entries.
 * 7. Styles edit parameters and values were translated via tags "edit_style" and "edit_style_values" entries.
 * 8. Helper function localize() is provided to get localized strings in code, they were taken from tag "misc".
 */

#define USERSTYLESDIR	"/mnt/ext1/system/config/"

enum PositionMode{
	positionPage = 0,		// default: PocketBook original mode - calculated pages positions
	positionParagraph = 1,	// Positions by paragraph number
	positionPercent = 2,	// Positions by percent, calculated from paragraph number
	positionPercentFloat = 3,	// Positions by percent, displayed as float, calculated from paragraph number
};

int rotate_mode=0;      // 0=360РїС—Р…, 1=180РїС—Р…, 2=180РїС—Р…+pageflip forward, 3=180РїС—Р…+pageflip both, 4=fast flip forward, 5=fast flip back/forward, 6=like 5, but reversed
int rotate_angle=1;     // Rotate angle in 5РїС—Р… steps, 0=5РїС—Р…, 1=10РїС—Р… etc.
int invertoption=0;     // 0=always, 1=never, 2=not on go forward, 3=special leaf mode
int	invertlimit=0;		// Limit for counter of continuous draws without inversion, when reached inversion will be done, 0 - no limit
int	hide_status_dialog_timeout=0;		// if non-zero hide status bar completely, show/hide handler shows message with info instead during specified time in seconds
int font_change_step=5;	// Cyclic font change step
int spaces_variability_limits=0;	// Adjustment of space width to justify text row. 0-off, 1-10%, 2-20% etc, MUST be less than 10.
int hyphenation_min_length=3;	// minimal length of word to allow hyphenation
int autopageflip=0;		// timeout for automatic page flip in seconds, 0 - off
int footnote_mode=0;	// 0=usual, 1=short footnotes displayed in baloon
int footnote_ext=1;		// 0=off, 1=use back_link key to show next footnote (when the last footnote on page reached go to the first one)
int position_mode=positionPage;	// position mode for display and selection

#define USE4BPP 0

extern char *encoding_override;
extern int break_override;
extern int hyph_override;
extern int book_open_ok;
char detectedlang[4];
int	notinvertedpages = 0;
bool pagehaspicture = false;
bool prevpagehaspicture = false;
int status_panel_old = 1;
bool styles_has_changed = false;

// Status replacement dialog content flags
int hsdc_filename = 0;
int hsdc_title = 1;
int hsdc_author = 0;
int hsdc_series = 0;
int hsdc_genres = 0;
int hsdc_separator = 0;
int hsdc_chapter = 1;
int hsdc_pages = 1;
int hsdc_datetime = 1;
int hsdc_battery = 1;

// statistics
ZLPageStats	*stats = NULL;
ZLTextStatsFileWriter	*stfile = NULL;

bool reading_mode = false;
bool autoflip_enabled = true;		// switched on and off by special action (KA_mpdf)
bool autoflip_keep_going = false;	// temporary switch off autoflip by PAGE_BACK and LOCK
bool autoflip_interface_on = true;	// temporary switch off autoflip due to interface manipulations

extern "C" const ibitmap searchbm;
extern "C" const ibitmap arrow_back;

extern "C" const ibitmap ci_font, ci_enc, ci_spacing, ci_margins, ci_textfmt, ci_hyphen, ci_about, ci_invert, ci_rotate;;

// 3x3 menu picture for 2.x.x firmware
// Note! When PB SDK will be released this should be replaced with correct new menu
extern "C" const ibitmap fbreader_menu;

static char *strings3x3[9];

static char *spacing_variants[] = { "50", "60", "70", "80", "90", "100", "110", "120", "150", "200", NULL };
static char *border_variants[] = { "@border_small", "@border_medium", "@border_large", "@border_custom", NULL };
static char *border_cust_variants[] = { "0","4","8","12","16","20","24","28","32","36","40","48", NULL };
static char *textfmt_variants[] = { "@auto", "@fmt_newline", "@fmt_emptyline", "@fmt_indent", NULL };
static char *rotate_variants[] = { "360°", "180°", "@180°+slow next page", "@180°+slow prev/next page", "@180°+FAST next page", "@180°+FAST prev/next page", "@180°+FAST next/prev page", NULL  };  //UTF-8
static char *angle_variants[] = { "20°","25°","30° (default)","35°","40°","45°","50°","55°","60°","65°","70°", NULL };
static char *footnote_variants[] = { "@normal","@small in baloon", NULL};
static char *position_variants[] = { "@pages","@paragraphs","@percent","@percent(float display)", NULL};
// the values below were measured return values of the G sensor
int angles_measured[11];
static char *invert_variants[] = { "@always","@never","@not for next",/*"quick page flip mode",*/ NULL};
static char *invertlimit_variants[] = { "@off","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15", NULL };
static char *statusdialog_timeouts[] = { "@enable status","3","4","5","6","7","8","9","10","@until key pressed", NULL };
static char *font_step_variants[] = { "1","2","3","4","5","6","7","8","9","10", NULL };
static char *spaces_variability_variants[] = { "@off","10%","20%","30%","40%","50%", NULL };
static char *autoflip_variants[] = { "@off","5","7","9","10","11","12","15","18","22","26","30","35","45","60", NULL };
char autostr[16];
char *hyph_variants[] = { "@Off", autostr, "@en","@cs","@de","@eo","@es","@fi","@fr","@it","@no","@pt","@ru","@sv","@uk", NULL};
char *hyph_variants_values[] = { "off", "auto", "en","cs","de","eo","es","fi","fr","it","no","pt","ru","sv","uk", NULL};
char *hyph_min_variants[] = { "3","4","5","6","7","8","9","10", NULL};

static char *encoding_variants[] = {

	"auto",
	"@enc_russian",
		":WINDOWS-1251",
		":ISO-8859-5",
		":IBM866",
		":KOI8-R",
		":KOI8-U",
	"@enc_west_eur",
		":ISO-8859-1",
		":ISO-8859-15",
		":WINDOWS-1252",
	"@enc_east_eur",
		":ISO-8859-2",
		":ISO-8859-4",
		":ISO-8859-13",
		":WINDOWS-1257",
	"@enc_mid_east",
		":ISO-8859-8",
		":WINDOWS-1255",
	"@enc_east_asia",
		":Big5",
		":GBK",
	"UTF-8",
	NULL

};

static void save_curr_styles();
static void clean_config_style_entries();

// variables for list of available style files
std::vector<std::string>	style_names_list;
char	**styles_list = NULL;
// variables for dynamic style edit menu
std::list<std::string>	style_entry_name_strings;
std::list<std::string>	style_entry_strings;
iconfigedit	*style_entries = NULL;
int	style_entry_count = 0;

char *style_bool_variants[] = { "@default", "@true", "@false", NULL };
char *style_hlink_variants[] = { "         ", "@internal", "@external", NULL };
char *style_align_variants[] = { "@default", "@left", "@right", "@center", "@justify", NULL };
char *infocontent_variants[] = { "@off", "@on", NULL };

iconfigedit	styles_ce[] = {
  { CFG_INFO,   &ci_about,  "@Save current style as...", NULL, "style_save", NULL, NULL, NULL },
  { CFG_INDEX,  &ci_about,  "@Load style...", NULL, "style_load", "0", NULL, NULL },
  { CFG_INFO,   &ci_about,  "@Load default style", NULL, "style_load_default", NULL, NULL, NULL },
  { CFG_INFO,   &ci_about,  "@Edit current style", NULL, "style_edit", NULL, NULL, NULL },
  { 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

iconfigedit	infocontent_ce[] = {
  { CFG_INDEX,  &ci_invert, "@File name and type",			NULL, "hsdc_filename",	"0", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Book title",					NULL, "hsdc_title",		"1", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Book author",					NULL, "hsdc_author",	"0", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Book series",					NULL, "hsdc_series",	"0", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Book genres",					NULL, "hsdc_genres",	"0", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@String-separator",			NULL, "hsdc_separator",	"0", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Current chapter",				NULL, "hsdc_chapter",	"1", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Current position and total",	NULL, "hsdc_pages",		"1", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Date and time",				NULL, "hsdc_datetime",	"1", infocontent_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Battery info",				NULL, "hsdc_battery",	"1", infocontent_variants, NULL },
  { 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

iconfigedit	advanced_ce[] = {
  { CFG_CHOICE, &ci_font,   "@Font change step",NULL, "fontchangestep", "5", font_step_variants, NULL },
  { CFG_CHOICE, &ci_hyphen, "@Hyphenation min. word length",NULL, "hyphenationmin", "3", hyph_min_variants, NULL },
  { CFG_INDEX,  &ci_hyphen, "@Spaces adjustment limit",NULL, "spacesvariability", "0", spaces_variability_variants, NULL },
  { CFG_CHOICE, &ci_margins,"@Left margin",NULL, "margin_left", "16", border_cust_variants, NULL },
  { CFG_CHOICE, &ci_margins,"@Right margin",NULL, "margin_right", "16", border_cust_variants, NULL },
  { CFG_CHOICE, &ci_margins,"@Top margin",NULL, "margin_top", "24", border_cust_variants, NULL },
  { CFG_CHOICE, &ci_margins,"@Bottom margin",NULL, "margin_bottom", "12", border_cust_variants, NULL },
  { CFG_SUBMENU,&ci_invert, "@Status replacement info content", NULL, "hidestatus_dialogcontent", NULL, NULL, infocontent_ce },
  { CFG_INFO,   &ci_font,   "@Re-enable non-TTF fonts",   NULL, "font_reenable", NULL, NULL, NULL },
  { 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

static iconfigedit fbreader_ce[] = {

  { CFG_INFO,   &ci_about,  "@About_book", NULL, "about", NULL, NULL, NULL },
  { CFG_FONT,   &ci_font,   "@Font",       NULL, "font", DEFREADERFONT, NULL, NULL },
  { CFG_CHOICE, &ci_enc,    "@Encoding",   NULL, "encoding", "auto", encoding_variants, NULL },
  { CFG_CHOICE, &ci_spacing,"@Linespacing",NULL, "linespacing", "100", spacing_variants, NULL },
  { CFG_INDEX,  &ci_margins,"@Pagemargins",NULL, "border", "1", border_variants, NULL },
  { CFG_INDEX,  &ci_hyphen, "@Hyphenation",NULL, "hyphenations", "1", hyph_variants, NULL },
  { CFG_INDEX,  &ci_textfmt,"@TextFormat", NULL, "preformatted", "0", textfmt_variants, NULL },
  { CFG_INDEX,  &ci_rotate, "@Rotate", NULL, "rotatemode", "0", rotate_variants, NULL },
  { CFG_INDEX,  &ci_rotate, "@Page turn angle", NULL, "rotateangle", "2", angle_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Invert screen (to black)", "", "invertmode", "0", invert_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Not inverted pages limit", "", "invertlimit", "0", invertlimit_variants, NULL },
  { CFG_INDEX,  &ci_invert, "@Disable status, show info instead", "", "hidestatus_dialogtimeout", "0", statusdialog_timeouts, NULL },
  { CFG_INDEX,  &ci_invert, "@Autoflip page interval(seconds)", "", "autoflip", "0", autoflip_variants, NULL },
  { CFG_INDEX,  &ci_textfmt,"@Footnote mode", "", "footnotemode", "0", footnote_variants, NULL },
  { CFG_INDEX,  &ci_textfmt,"@Position in text", "", "positionmode", "0", position_variants, NULL },
  { CFG_SUBMENU,&ci_textfmt,"@Advanced settings", NULL, "advanced", NULL, NULL, advanced_ce },
  { CFG_SUBMENU,&ci_font,   "@Styles",      NULL, "styles", NULL, NULL, styles_ce },
  { 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

static char *def_menutext[9] = {
	"@Goto_page", "@Exit", "@Search",
	"@Bookmarks", "@Menu", "@Rotate",
	"@Dictionary", "@Contents", "@Settings"
};

static const char *def_menuaction[9] = {
	"@KA_goto", "@KA_exit", "@KA_srch",
	"@KA_obmk", "@KA_none", "@KA_rtte",
	"@KA_dict", "@KA_cnts", "@KA_conf"
};

static bookinfo *bi;

tdocstate docstate;

static char *FileName;
static char *OriginalName;
static char *DataFile;
static char *ZetFile;
static char *book_title="";
static char *book_author="";
static int ready_sent = 0;
static int making_snapshot = 0;

static iconfig *gcfg, *fcfg;
static int ko;

static long long *pagelist=NULL;
static int pagelistsize=0;
static int npages;
static long long cpos;

static int soft_update = 0;

static tocentry *TOC;
static int toc_size;

static int bmkpages[32];

static char kbdbuffer[32];
static int search_mode = 0;

static char *keyact0[32], *keyact1[32];

typedef struct hlink_s {
	short x;
	short y;
	short w;
	short h;
	int kind;
	char *href;
} hlink;

static int links_mode = 0;
static int link_back = -1;
static hlink links[MAXLINKS];
static int nlinks=0, clink;

static int calc_in_progress = 0;
static int calc_position_changed = 0;
static int current_position_changed = 0;
static int calc_current_page;
static int calc_orientation = -1;
static long long calc_current_position;

extern ZLApplication *mainApplication;
BookTextView *bookview;
FootnoteView *footview;

unsigned char *screenbuf;
int use_antialiasing;
static int orient;
int imgposx, imgposy, imgwidth, imgheight, scanline;
int lock_drawing=0;
int no_save_state=0;
void *ballast=NULL;

static ibitmap *m3x3;
static ibitmap *bmk_flag;
static ibitmap *bgnd_p, *bgnd_l;
static irect textarea;

std::vector<std::string> xxx_notes;

struct xxx_link {
	int x1, y1, x2, y2;
	int kind;
	std::string id;
	bool next;
};

std::vector<struct xxx_link> xxx_page_links;

struct xxx_toc_entry {
	int paragraph;
	int level;
	std::string text;
};

std::vector<xxx_toc_entry> xxx_myTOC;

iv_wlist *xxx_wordlist=NULL;
int xxx_wlistlen=0, xxx_wlistsize=0;

int glb_page;

bool rotatetimerset=false;

static void rotate_timer();

bool autofliptimerset=false;

static void autoflip_timer();

// Denis //

#define EPSX 70
#define EPSY 70
#define MENUMARGIN 150
#define BOOKMARKMARGIN 200
#define SCROLLPAGEMARGIN 200
#define FONTMARGIN 100

#define MinFontSize 10 // минимальный размер шрифта при циклическом переборе
#define MaxFontSize 50 // максимальный размер шрифта при циклическом переборе

static int x0, y0;
static bool Long, Link, AddBkMark;

//////////////////////////////////////////////////////////////////////////////
// Localization
//////////////////////////////////////////////////////////////////////////////
/// variable to hold localization state
static bool	bConfigLocalized = false;
static bool	bMenuLocalized = false;

class ConfigStackEntry
{
public:
	ConfigStackEntry(iconfigedit *pC, const ZLResource &r): pConfig(pC),res(&r) {}

	iconfigedit			*pConfig;
	const ZLResource	*res;
};

inline void unsafe_set_string(char* &str,const ZLResource &val) {
	if(val.hasValue())
		str=const_cast<char*>(val.value().c_str());
	else str+=1;
}

static void apply_localization2variants(char** var,const ZLResource &res) {
	for(unsigned int j=0;var[j];++j)
		if(var[j][0]=='@')
			unsafe_set_string(var[j],res[var[j]+1]);
}

/// walk config structure tree and convert any @-started string to appropriate localized text
static void apply_localization2config() {
	if(bConfigLocalized) return;

	std::list<ConfigStackEntry>	vStack;
	const ZLResource	*res = &ZLResource::resource("config");
	iconfigedit	*pCfg;
	unsigned int i,j;
	// walk through config items
	vStack.push_back(ConfigStackEntry(fbreader_ce,ZLResource::resource("config")));
	for(;!vStack.empty();vStack.pop_front())
	{
		pCfg = vStack.front().pConfig;
		res = vStack.front().res;
		for(i=0;pCfg[i].type>0;++i)
		{
			// check item
			if(!pCfg[i].name) continue;
			// get resource entry
			const ZLResource &item_res = (*res)[pCfg[i].name];
//			fprintf(stderr,"res: %s -> %s\n",pCfg[i].name,item_res.value().c_str());
			// translate item name
			if(pCfg[i].text[0]=='@')
				unsafe_set_string(pCfg[i].text,item_res);
			// translate default value
			if(pCfg[i].deflt && pCfg[i].deflt[0]=='@')
				unsafe_set_string(pCfg[i].deflt,item_res[pCfg[i].deflt+1]);
			// translate values array
			if(pCfg[i].variants)
				apply_localization2variants(pCfg[i].variants,item_res);
			// if submenu exists push it to stack
			if(pCfg[i].submenu)
				vStack.push_back(ConfigStackEntry(pCfg[i].submenu,item_res));
		}
	}
	// set flag
	bConfigLocalized = true;
}

/// walk menu structure and convert any @-started string to appropriate localized text
static void apply_localization2menu() {
	if(bMenuLocalized) return;

	unsigned int i;
	const ZLResource &res = ZLResource::resource("menu");
	// walk through menu items
	for(i=0;i<9;++i)
		if(strings3x3[i][0]=='@')
			unsafe_set_string(strings3x3[i],res[strings3x3[i]+1]);
	// set flag
	bMenuLocalized = true;
}

/// get localized string from custom tag
static char* localize(const char* tag,const char* name) {
	if(!name) return "<empty>";
	const ZLResource &res = ZLResource::resource(tag);
	const ZLResource &val = res[name];
	if(val.hasValue())
		return const_cast<char*>(val.value().c_str());
	return const_cast<char*>(name);
}

/// get localized string from custom tag
static char* localize(const std::string tag,const std::string name) {
	return localize(tag.c_str(),name.c_str());
}

static const ZLResource *misc_resources = NULL;

/// get localized string from "misc"
static char* localize(const char* name) {
	if(!name) return "<empty>";
	if(!misc_resources)
		misc_resources=&ZLResource::resource("misc");
	const ZLResource &val = (*misc_resources)[name];
	if(val.hasValue())
		return const_cast<char*>(val.value().c_str());
	return const_cast<char*>(name);
}

/// get localized string from "misc"
static char* localize(const std::string name) {
	return localize(name.c_str());
}

/// get localized string from specific resource
static char* localize(const ZLResource &res,const char* name) {
	if(!name) return "<empty>";
	const ZLResource &val = res[name];
	if(val.hasValue())
		return const_cast<char*>(val.value().c_str());
	return const_cast<char*>(name);
}

/// get localized string from custom tag
static char* localize(const ZLResource &res,const std::string name) {
	return localize(res,name.c_str());
}


//////////////////////////////////////////////////////////////////////////////
// routines to start/stop features that works only in reading mode
//////////////////////////////////////////////////////////////////////////////
inline bool is_autoflip_enabled() {
	return ((autopageflip>0) && autoflip_enabled && autoflip_keep_going && autoflip_interface_on);
}

inline void autoflip_update() {
	if(is_autoflip_enabled()) {// enable
		if (!autofliptimerset) {
			SetHardTimer("AutoFlipPage", autoflip_timer, autopageflip*1000);
			autofliptimerset = true;
		}
	} else {// disable
		if (autofliptimerset) {
			ClearTimer(autoflip_timer);
			autofliptimerset = false;
		}
	}
}

inline void reading_mode_on() {
	// enable page flip by rotation
	if (rotate_mode>3 && !rotatetimerset) {
		SetWeakTimer("RotatePage", rotate_timer, 200);
		rotatetimerset = true;
	}
	
	// enable auto page flip
	autoflip_interface_on = true;
	autoflip_update();

	if (!reading_mode) {
		// force redraw with inversion
		notinvertedpages = 32767;
		// clear previous pictute flag becase full redraw is forced
		prevpagehaspicture = false;
		// set flag
		reading_mode = true;
	}
}

inline void reading_mode_off(bool do_reading_mode_off=true) {
	// stop page flip by rotation
	if (rotatetimerset) {
		ClearTimer(rotate_timer);
		rotatetimerset = false;
	}
	
	// stop auto page flip
	autoflip_interface_on = false;
	autoflip_update();

	if (reading_mode && do_reading_mode_off) {
		// clear flag
		reading_mode = false;
	}
}


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Denis //

static void draw_searchpan() {
	DrawBitmap(ScreenWidth()-searchbm.width-10, ScreenHeight()-searchbm.height-35, &searchbm);
}

static int is_footnote_mode() {

	return (((FBReader *)mainApplication)->getMode() == FBReader::FOOTNOTE_MODE);

}

static long long get_position() {

	ZLTextWordCursor cr = is_footnote_mode() ? footview->startCursor() : bookview->startCursor();
	return pack_position(cr.paragraphCursor().index(), cr.wordNumber(), cr.charNumber());

}

static long long get_end_position() {

	ZLTextWordCursor cr = is_footnote_mode() ? footview->endCursor() : bookview->endCursor();
	if (cr.isNull()) {
		mainApplication->refreshWindow();
		cr = is_footnote_mode() ? footview->endCursor() : bookview->endCursor();
	}
	if (cr.isNull()) return get_position();
	return pack_position(cr.paragraphCursor().index(), cr.wordNumber(), cr.charNumber());

}

static long long get_bookview_position() {

	ZLTextWordCursor cr = bookview->startCursor();
	return pack_position(cr.paragraphCursor().index(), cr.wordNumber(), cr.charNumber());

}

static void draw_bmk_flag(int update) {
	int i, x, y, bmkset=0;
	long long s, e;
	if (! bmk_flag) return;
	if (is_footnote_mode()) return;
	s = get_position();
	e = get_end_position();
	for (i=0; i<docstate.nbmk; i++) {
		if (docstate.bmk[i] >= s && docstate.bmk[i] < e) bmkset = 1;
	}
	if (! bmkset) return;
	x = ScreenWidth()-bmk_flag->width;
	y = 0;
	DrawBitmap(x, y, bmk_flag);
	if (update) PartialUpdate(x, y, bmk_flag->width, bmk_flag->height);
}

static void printpos(char *s, long long n) {

	int p, w, l, cpage;
	unpack_position(cpos, &p, &w, &l);
	cpage = position_to_page(cpos);
	fprintf(stderr, "%s %i  %lld %i %i %i\n", s, cpage, cpos, p, w, l);

}

ifont* nrFont = NULL;

static void repaint(int update_mode) {

	reading_mode_on();

	/* update_mode: -1=none 0=soft 1=full */

	char buf[256], *title;
	int cpage, percent = -1;
	const ibitmap *icon, *bgnd;

	ClearScreen();

	bgnd = (orient == 0 || orient == 3) ? bgnd_p : bgnd_l;
	if (bgnd) DrawBitmap(0, 0, bgnd);

	restore_current_position();
//	mainApplication->refreshWindow();
	Stretch(screenbuf, USE4BPP ? IMAGE_GRAY4 : IMAGE_GRAY2, imgwidth, imgheight, scanline,
		imgposx, imgposy, imgwidth, imgheight, 0);

	icon = NULL;
	if (making_snapshot) {
		icon = PANELICON_LOAD;
		strcpy(buf, localize("snapshot_info"));
		title = NULL;
		percent = -1;
	} else if (calc_in_progress) {
		icon = PANELICON_BOOK;
		sprintf(buf, "...       ");
		title = book_title;
		percent = -1;
	} else if (! is_footnote_mode()) {
		switch (position_mode){
		case positionPage:
			cpage = position_to_page(get_position());
			sprintf(buf, "  %i / %i", cpage, npages);
			percent = (cpage * 100) / npages;
			break;
		case positionParagraph:
		case positionPercent:
		case positionPercentFloat:
			{
				int paraFull = bookview->model()->paragraphsNumber();
				int para, word, letter;
				unpack_position(get_position(), &para, &word, &letter);
				if (position_mode == positionPercent)
					buf[0] = 0;
				else if (position_mode == positionPercentFloat)
					sprintf(buf, "  %.2f%%", (paraFull > 0) ? (((float)para * 100.0) / (float)paraFull) : 100.0);
				else
					sprintf(buf, "  %i § %i", para+1, paraFull);
				percent = (paraFull > 0) ? ((para * 100) / paraFull) : 100;
			}
			break;
		}
		title = book_title;
	} else {
		buf[0] = 0;
		title = book_title;
		percent = -1;
	}

	if (links_mode && ! making_snapshot) {
		icon = NULL;
		if (link_back >= 0) DrawBitmap(0, 0, &arrow_back);
		invert_current_link();
		sprintf(buf, localize("choose_link"));
	}
	if (search_mode && ! making_snapshot) {
		draw_searchpan();
	}


	//shared_ptr<ZLTextModel> model = bookview->model();
	//int npar = model->paragraphsNumber();
	//if (npar <= 0) npar = 1;
	//unpack_position(get_position(), &p, &w, &l);

/*	if (use_antialiasing==3) {   // no grayscale mode!
		int hpanel=PanelHeight();
		if (!calc_in_progress && hpanel>0) {
			if (!nrFont)
				nrFont = OpenFont("LiberationSans", 20, 0);
			SetFont(nrFont, BLACK);
			sprintf(buf, "%i / %i", cpage, npages);
			DrawString(20, ScreenHeight()-hpanel, buf);
		}
		update_mode=3;
	} else {*/
		DrawPanel((ibitmap *)icon, buf, title, percent);
		draw_bmk_flag(0);
//	}

	if (invertoption==1 && update_mode != -1) //второе условие отключает частичное обновление для режма закладок
		update_mode=0;
	else if (invertoption==2 && update_mode==2)
		update_mode=0;

	switch (update_mode) {
		case 0:
			if ((invertlimit==0 || notinvertedpages<invertlimit) && !(pagehaspicture||prevpagehaspicture))
			{
				SoftUpdate();
				++notinvertedpages;
				break;
			}
		case 2:
		case 1: FullUpdate(); notinvertedpages=0; break;
//		case 3:PartialUpdateBW(0,0,ScreenWidth(),ScreenHeight()); break;
	}

	if(stats && stfile) {
		if(stats->getNewDataFlag()) {
			stfile->writePageStats(stats);
			stats->clearNewDataFlag();
		}
	}
}

static int one_page_back(int update_mode) {
	if(stfile) stfile->writeOperation(SFO_PrevPage);

	restore_current_position();
	mainApplication->refreshWindow();
	long long tmppos=get_position();
	int p = position_to_page(tmppos);
//	if (is_footnote_mode() || p > npages || p == 0) {
	if (is_footnote_mode() || p >= npages || p == 0) {
		mainApplication->doAction(ActionCode::LARGE_SCROLL_BACKWARD);
		mainApplication->refreshWindow();
	} else {
		set_position(page_to_position(p-1));
		mainApplication->refreshWindow();
	}
	cpos = get_position();
	printpos("< ", cpos);
	calc_position_changed = 1;
	if (cpos == tmppos && is_footnote_mode()) {
		mainApplication->doAction(ActionCode::CANCEL);
		mainApplication->refreshWindow();
		cpos = get_position();
	}
	clink = 0;
	repaint(update_mode);

	return (cpos != tmppos);
}

static int one_page_forward(int update_mode) {
	if(stfile) stfile->writeOperation(SFO_NextPage);

	restore_current_position();
	long long tmppos=get_position();
	int p = position_to_page(tmppos);
	if (is_footnote_mode() || p >= npages) {
		mainApplication->doAction(ActionCode::LARGE_SCROLL_FORWARD);
	} else {
		set_position(page_to_position(p+1));
		mainApplication->refreshWindow();
	}
	cpos = get_position();
	printpos("> ", cpos);
	calc_position_changed = 1;
	clink = 0;
	repaint(update_mode);

	if(stats)
		fprintf(stderr,"@@@ Page: %u words, %u images\n",stats->getWordCount(),stats->getImageCount());

	return (cpos != tmppos);
}

static void prev_page() { autoflip_keep_going=false; one_page_back(1); }
static void next_page() { autoflip_keep_going=true; one_page_forward(2); }

static void prev_section() {
	restore_current_position();
	if (is_footnote_mode()) {
		mainApplication->doAction(ActionCode::CANCEL);
	}
	mainApplication->doAction(ActionCode::GOTO_PREVIOUS_TOC_SECTION);
	cpos = get_position();
	calc_position_changed = 1;

	autoflip_keep_going=true;
	clink = 0;
	repaint(1);
}

static void next_section() {
	restore_current_position();
	if (is_footnote_mode()) {
		mainApplication->doAction(ActionCode::CANCEL);
	}
	mainApplication->doAction(ActionCode::GOTO_NEXT_TOC_SECTION);
	cpos = get_position();
	calc_position_changed = 1;

	autoflip_keep_going=true; 
	clink = 0;
	repaint(1);
}

static void jump_pages(int n) {
	if(stfile) stfile->writeOperation(SFO_OtherPage);
	autoflip_keep_going=true; 

	char buf[16];
	int x, y, w, h, cpage;
	int para, word, letter, paraFull, percent;

/*	if (invertoption==3) {
		int old_use_antialiasing=use_antialiasing;
		use_antialiasing=3;
		(n < 0) ? one_page_back(0) : one_page_forward(0);
		use_antialiasing=old_use_antialiasing;
	} else*/
	if (calc_in_progress) {
		(n < 0) ? prev_page() : next_page();
	} else {
		switch (position_mode){
		case positionPage:
			cpage = position_to_page(cpos);
			cpage += n;
			if (cpage < 1) cpage = 1;
			if (cpage > npages) cpage = npages;
			cpos = page_to_position(cpage);
			set_position(cpos);
			sprintf(buf, "%i", cpage);
			break;
		case positionParagraph:
			paraFull = bookview->model()->paragraphsNumber();
			unpack_position(cpos, &para, &word, &letter);
			para += n * 3;
			if (para >= paraFull) para=paraFull-1;
			if (para < 0) para=0;
                        cpos = pack_position(para, 0, 0);
			set_position(cpos);
			sprintf(buf, "%i", para);
			break;
		case positionPercent:
		case positionPercentFloat:
			paraFull = bookview->model()->paragraphsNumber();
			if (paraFull > 0){
				unpack_position(cpos, &para, &word, &letter);
				percent = para * 100 / paraFull;
				if (n > 0) percent+=2;
				else percent-=2;
				para = paraFull * percent / 100;
				if (para >= paraFull) para=paraFull-1;
				if (para < 0) para=0;
                                cpos = pack_position(para, 0, 0);
                                set_position(cpos);
				sprintf(buf, "%i", percent);
			}
			break;
		}
		w = 50;
		h = (menu_n_font->height*3) / 2;
		x = ScreenWidth()-w-5;
		y = ScreenHeight()-h-30;
		FillArea(x+1, y+1, w-2, h-2, WHITE);
		DrawRect(x+1, y, w-2, h, BLACK);
		DrawRect(x, y+1, w, h-2, BLACK);
		SetFont(menu_n_font, BLACK);
		DrawTextRect(x, y, w, h, buf, ALIGN_CENTER | VALIGN_MIDDLE);
		PartialUpdateBW(x, y, w, h);

	}
	clink = 0;

}

static void start_recalc_timer() { calc_pages(); }

static void font_change(int d) {

	char buf[256], *p;
	int size;

	restore_current_position();

	char *xfont = ReadString(fcfg, "font", DEFREADERFONT);
	strcpy(buf, xfont);
	p = strchr(buf, ',');
	if (p) {
		size = atoi(p+1);
	} else {
		p = buf+strlen(buf);
		size = 12;
	}

	size += d;

	if (d < 0) {
		if (size < MinFontSize) size = MaxFontSize;  
	} else {
		if (size > MaxFontSize) size = MinFontSize; 
	}

	sprintf(p, ",%i", size);
	WriteString(fcfg, "font", buf);
	apply_config(1, 1);
	repaint(1);

}

int human_size(char *buf, unsigned long long bsize, unsigned long long nblocks);

#define SHP_CHK_FIRST_STRING() {if(first_string) first_string=false; else cnt+=snprintf(buf+cnt,size-cnt,"\n");}

static void show_hide_panel() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	restore_current_position();
	if(hide_status_dialog_timeout) {
		char buf[2048],sbuf[128];
		int cpage, i, size=2040, cnt=0;
		int para, word, letter, percent, paraFull;
		bool first_string = true;
		buf[0] = 0;

		if (hsdc_filename) {
			char *p = strrchr(OriginalName, '/');
			SHP_CHK_FIRST_STRING();
			cnt += snprintf(buf+cnt, size-cnt, "%s", p ? p+1 : OriginalName);

			if (bi->typedesc && bi->typedesc[0]) {
				cnt += snprintf(buf+cnt, size-cnt, "(%s)", GetLangText(bi->typedesc));
			}
		}


		if (hsdc_title && bi->title && bi->title[0]) {
			SHP_CHK_FIRST_STRING();
			cnt += snprintf(buf+cnt, size-cnt, "%s:  %s", localize("Title"), bi->title);
		}

		if (hsdc_author && bi->author && bi->author[0]) {
			SHP_CHK_FIRST_STRING();
			cnt += snprintf(buf+cnt, size-cnt, "%s:  %s", localize("Author"), bi->author);
		}

		if (hsdc_series && bi->series && bi->series[0]) {
			SHP_CHK_FIRST_STRING();
			cnt += snprintf(buf+cnt, size-cnt, "%s:  %s", localize("Series"), bi->series);
		}

		if(hsdc_genres) {
			for (i=0; i<10; i++) {
				if (bi->genre[i] == NULL) break;
				if (bi->genre[i][0]) {
					SHP_CHK_FIRST_STRING();
					cnt += snprintf(buf+cnt, size-cnt, "%s:  %s", localize("Genre"), localize("genres",bi->genre[i]));
				}
			}
		}

		if(hsdc_separator) {
			SHP_CHK_FIRST_STRING();
		}

		if(hsdc_chapter) {
			int para,word,chr,i,tsz,chapter=-1;
			unpack_position(get_bookview_position(),&para,&word,&chr);
			// search current chapter
			tsz = xxx_myTOC.size();
			for (i=0; i<tsz; ++i) {
				if (xxx_myTOC[i].paragraph != -1)
					if(xxx_myTOC[i].paragraph <= para) chapter=i;
					else break;
			}

			if (chapter > -1) {
				SHP_CHK_FIRST_STRING();
				int cnt_prev = cnt;
				cnt += snprintf(buf+cnt, size-cnt, "%s:  %s", localize("Chapter"), xxx_myTOC[chapter].text.c_str());
				for(;cnt_prev<cnt;++cnt_prev)
					if (buf[cnt_prev] == '\r' || buf[cnt_prev] == '\n') buf[cnt_prev]=' ';
			}
/*			else {
				SHP_CHK_FIRST_STRING();
				cnt += snprintf(buf+cnt, size-cnt, "%s:  paragraph %d(toc=%d)", localize("Chapter"), para, toc_size);
			}*/
		}

		if(hsdc_pages) {
			// write pages info
			human_size(sbuf, 1, bi->size);
			if (making_snapshot) {
				SHP_CHK_FIRST_STRING();
				cnt += sprintf(buf+cnt, "%s", localize("snapshot_info"));
			} else if (calc_in_progress) {
				SHP_CHK_FIRST_STRING();
				cnt += sprintf(buf+cnt, "... %s ...", localize("calculating_page_count"));
			} else if (! is_footnote_mode()) {
				switch (position_mode){
				case positionPage:
					cpage = position_to_page(get_bookview_position());
					SHP_CHK_FIRST_STRING();
					cnt += sprintf(buf+cnt, "%i%%: %i / %i %s,  %s", ((cpage*100)/npages), cpage, npages, localize("pages"),sbuf);
					break;
				case positionParagraph:
					paraFull = bookview->model()->paragraphsNumber();
					if (!paraFull) paraFull=1;
					unpack_position(get_bookview_position(), &para, &word, &letter);
					SHP_CHK_FIRST_STRING();
					cnt += sprintf(buf+cnt, "%i%%: %i § %i %s,  %s", ((para*100)/paraFull), para, paraFull, localize("para"),sbuf);
					break;
				case positionPercent:
					paraFull = bookview->model()->paragraphsNumber();
					if (!paraFull) paraFull=1;
					unpack_position(get_bookview_position(), &para, &word, &letter);
					SHP_CHK_FIRST_STRING();
					cnt += sprintf(buf+cnt, "%i%%: %s", ((para*100)/paraFull),sbuf);
					break;
				case positionPercentFloat:
					paraFull = bookview->model()->paragraphsNumber();
					if (!paraFull) paraFull=1;
					unpack_position(get_bookview_position(), &para, &word, &letter);
					SHP_CHK_FIRST_STRING();
					cnt += sprintf(buf+cnt, "%.2f%%: %s", (((float)para*100.0)/(float)paraFull),sbuf);
					break;
				}
			} else {
	//			buf[0] = 0;
			}
		}

		bool datetime_printed = false;
		if(hsdc_datetime) {
			// write datetime
			time_t		curr_time;
			struct tm	exp_time;
			time(&curr_time);
			localtime_r(&curr_time,&exp_time);
			SHP_CHK_FIRST_STRING();
			cnt += sprintf(buf+cnt,"%s, %.02d:%.02d",DateStr(curr_time),exp_time.tm_hour,exp_time.tm_min);
			datetime_printed = true;
		}

		if(hsdc_battery) {
			// for portrait - go to next row
			if(first_string) first_string=false;
			else {
				int _orient = GetOrientation();
				if((_orient==0) || (_orient==3) || !datetime_printed) strcpy(buf+cnt,"\n");
				else strcpy(buf+cnt,"  ");
				cnt += strlen(buf+cnt);
			}
			// write external info
			if(IsCharging()) cnt+=sprintf(buf+cnt, "..%s..  %d°C",localize("charging"),GetTemperature());
			else {
				int	power_raw = GetBatteryPower();
	//			int power = power_raw * 4;	// calculate percent at 25 base, i.e. *100/25
	//			if(power>100) power=100;
	//			cnt += sprintf(buf+cnt, "%d%% battery  t%dC",power_raw,GetTemperature());
				cnt += sprintf(buf+cnt, "%d%% %s",power_raw,localize("battery"));
				cnt += sprintf(buf+cnt, "  %d°C",GetTemperature());
			}
		}

		autoflip_interface_on = false;
		autoflip_update();
		Message(ICON_INFORMATION,localize("BookInformation"),buf,(hide_status_dialog_timeout>8) ? 32000 : (hide_status_dialog_timeout+2)*1000);
		autoflip_interface_on = true;
		autoflip_update();
	} else {
		SetPanelType((PanelHeight() == 0) ? 1 : 0);
		apply_config(1, 1);
		repaint(1);
	}

}

static void apply_config(int recalc, int canrestart) {

	char buf[256], *p, *newenc;
	int size, newfmt;

	char *xfont = ReadString(fcfg, "font", DEFREADERFONT);
	strcpy(buf, xfont);
	p = strchr(buf, ',');
	if (p) *(p++) = 0;
	size = p ? atoi(p) : 12;
	fprintf(stderr,"Font in config: %s\n",buf);
	ifont *f = OpenFont(buf, size, 1);
	fprintf(stderr,"Loaded font: %s\n",f->family);

	ZLStringOption &ffoption = ZLTextStyleCollection::instance().baseStyle().FontFamilyOption;
	if(ffoption.value()!=f->family) styles_has_changed=true;
	ffoption.setValue(f->family);

	ZLIntegerRangeOption &fsoption = ZLTextStyleCollection::instance().baseStyle().FontSizeOption;
	if(fsoption.value()!=size) styles_has_changed=true;
	fsoption.setValue(size);

	ZLBooleanOption &boldoption = ZLTextStyleCollection::instance().baseStyle().BoldOption;
	boldoption.setValue(f->isbold ? true : false);

	ZLBooleanOption &italicoption = ZLTextStyleCollection::instance().baseStyle().ItalicOption;
	italicoption.setValue(f->isitalic ? true : false);

	int linespacing = ReadInt(fcfg, "linespacing", 100);
	ZLIntegerOption &lspaceoption = ZLTextStyleCollection::instance().baseStyle().LineSpacePercentOption;
	lspaceoption.setValue(linespacing);

	ZLTextStyleCollection::instance().SetUserDelta(0); /////.....

	// save styles if they were changed
	if(styles_has_changed) {
		save_curr_styles();
		styles_has_changed = false;
	}

	int old_hyph_override=hyph_override;
	hyph_override = ReadInt(fcfg, "hyphenations", 1);

	FBIndicatorStyle &indicatorInfo = bookview->commonIndicatorInfo();
	indicatorInfo.ShowOption.setValue(false);

	newenc = ReadString(fcfg, "encoding", "auto");
	newfmt = ReadInt(fcfg, "preformatted", 0);
	if (canrestart && (strcmp(newenc, docstate.encoding) != 0 || newfmt != docstate.preformatted ||
		old_hyph_override!=hyph_override)) {
		/*
			ShowHourglass();
			xxx_myTOC.clear();
			FBReader *fbr = (FBReader *)mainApplication;
			strncpy(docstate.encoding, newenc, 15);
			fbr->openFile(FileName);
		*/
		//save_state();
		//fprintf(stderr, "OpenBook(\"%s\")\n", OriginalName);
		ShowHourglass();
		OpenBook(OriginalName, NULL, 0);
	}

	invertoption = ReadInt(fcfg, "invertmode", 0);
	rotate_mode = ReadInt(fcfg, "rotatemode", 0);
	rotate_angle = ReadInt(fcfg, "rotateangle", 2);
	invertlimit = ReadInt(fcfg, "invertlimit", 0);
	int prev_hide_status_dialog_timeout = hide_status_dialog_timeout;
	hide_status_dialog_timeout = ReadInt(fcfg, "hidestatus_dialogtimeout", 0);
	font_change_step = ReadInt(fcfg, "fontchangestep", 5);
	spaces_variability_limits = ReadInt(fcfg, "spacesvariability", 0);
	hyphenation_min_length = ReadInt(fcfg, "hyphenationmin", 3);
	footnote_mode = ReadInt(fcfg, "footnotemode", 0);
	position_mode = ReadInt(fcfg, "positionmode", 0);

	// show/hide status panel
	if(prev_hide_status_dialog_timeout && hide_status_dialog_timeout==0)
		SetPanelType(status_panel_old);
	else if(hide_status_dialog_timeout) {
		if(prev_hide_status_dialog_timeout==0) 	status_panel_old=(PanelHeight() == 0)?0:1;
		SetPanelType(0);
	}

	hsdc_filename = ReadInt(fcfg, "hsdc_filename", 0);
	hsdc_title = ReadInt(fcfg, "hsdc_title", 1);
	hsdc_author = ReadInt(fcfg, "hsdc_author", 0);
	hsdc_series = ReadInt(fcfg, "hsdc_series", 0);
	hsdc_genres = ReadInt(fcfg, "hsdc_genres", 0);
	hsdc_separator = ReadInt(fcfg, "hsdc_separator", 0);
	hsdc_chapter = ReadInt(fcfg, "hsdc_chapter", 1);
	hsdc_pages = ReadInt(fcfg, "hsdc_pages", 1);
	hsdc_datetime = ReadInt(fcfg, "hsdc_datetime", 1);
	hsdc_battery = ReadInt(fcfg, "hsdc_battery", 1);

	// get autoflip timeout
	int autoflip_index = ReadInt(fcfg, "autoflip", 0);
	if(autoflip_index>0) {
		autopageflip = atoi(autoflip_variants[autoflip_index]);
		autoflip_enabled = true;
		autoflip_keep_going = false;
	}
	else autopageflip=0;
	fprintf(stderr,"auto page flip interval set to %d\n",autopageflip);

	orient = GetOrientation();
	int border = ReadInt(fcfg, "border", 1);
	ZLNXViewWidget *widget = (ZLNXViewWidget *) mainApplication->myViewWidget;

	if (orient == 0 || orient == 3) {
		imgposx = textarea.x;
		imgposy = textarea.y;
		imgwidth = textarea.w;
		imgheight = textarea.h;
	} else {
		imgposx = textarea.y;
		imgposy = textarea.x;
		imgwidth = textarea.h;
		imgheight = textarea.w;
	}
	if (imgposy+imgheight > ScreenHeight()-PanelHeight()) {
		imgheight = ScreenHeight()-PanelHeight()-imgposy;
	}

	if (border == 0) {
		imgposx += 4;
		imgposy += 10;
		imgwidth -= 8;
		imgheight -= 10;
	} else if (border == 1) {
		imgposx += 18;
		imgposy += 24;
		imgwidth -= 36;
		imgheight -= 36;
	} else if (border == 2) {
		imgposx += 36;
		imgposy += 36;
		imgwidth -= 72;
		imgheight -= 62;
	} else {
		// custom margin settings
		int left_margin = ReadInt(fcfg, "margin_left", 16);
		int right_margin = ReadInt(fcfg, "margin_right", 16);
		int top_margin = ReadInt(fcfg, "margin_top", 24);
		int bottom_margin = ReadInt(fcfg, "margin_bottom", 12);

		imgposx += left_margin;
		imgposy += top_margin;
		imgwidth -= left_margin + right_margin;
		imgheight -= top_margin + bottom_margin;
	}


	if (USE4BPP) {
		imgwidth = (imgwidth+3) & ~3;
		imgheight = (imgheight+3) & ~3;
		scanline = imgwidth/2;
	} else {
		imgwidth = (imgwidth+3) & ~3;
		imgheight = (imgheight+3) & ~3;
		scanline = imgwidth/4;
	}
	widget->setSize(imgwidth, imgheight);

	bookview->clearCaches();
	footview->clearCaches();
	mainApplication->refreshWindow();

	CloseFont(f);

	switch(position_mode){
		case positionPage:
		{
			switch (recalc) {
				case 0:
					fprintf(stderr, "clr_timer\n");
					ClearTimer(start_recalc_timer);
					break;
				case 1:
					fprintf(stderr, "calc_pages\n");
					calc_pages();
					break;
				case 2:
					if (calc_in_progress || calc_orientation == -1) {
						calc_pages();
					} else {
						if (calc_orientation != GetOrientation() && calc_orientation+GetOrientation() != 3) {
							fprintf(stderr, "set_timer\n");
							SetHardTimer("RECALC", start_recalc_timer, 5000);
						} else {
							fprintf(stderr, "clr_timer\n");
							ClearTimer(start_recalc_timer);
						}
					}
					break;
			}
			break;
		}
		case positionParagraph:
		case positionPercent:
		case positionPercentFloat:
			calc_in_progress = 0;
			break;
	}

	reading_mode_on();
}


static void rotate_handler(int n) {
	if(stfile) stfile->writeOperation(SFO_Interface);

	restore_current_position();
	int cn = GetOrientation();
	if (n == -1 || ko == 0 || ko == 2) {
		SetGlobalOrientation(n);
	} else {
		SetOrientation(n);
		WriteInt(fcfg, "orientation", n);
		SaveConfig(fcfg);
	}
	orient = GetOrientation();
	apply_config((cn+orient == 3) ? 0 : 1, 1);
	repaint(1);

}

//orientation: 0=portrait, 1=landscape 90, 2=landscape 270, 3=portrait 180

static int ornevt_handler(int n) {

	restore_current_position();
	int cn = GetOrientation();
	if (cn==n)
		return 0;   // nothing has changed!
	if (rotate_mode && (cn+n)!=3) {
		if (rotate_mode==1 || rotate_mode>3)
			return 0;
		bool prev=false;
		if (rotate_mode==3) {    // back+forward
			switch (cn) {
			case 0:prev=(n==1);break;
			case 1:prev=(n==3);break;
			case 2:prev=(n==0);break;
			case 3:prev=(n==2);break;
			}
		}
		if (prev)
			prev_page();
		else
			next_page();
		SetAutoPowerOff(0);
		SetAutoPowerOff(1);  // reset auto-power-off timer!
	} else {
		SetOrientation(n);
		apply_config(2, 1);
		repaint(1);
	}
	return 0;
}

static void save_state() {

  if (no_save_state) return;
  if (docstate.magic != 0x9751) return;
  restore_current_position();
  docstate.position = get_bookview_position();
  strncpy(docstate.encoding, ReadString(fcfg, "encoding", "auto"), 15);
  docstate.preformatted = ReadInt(fcfg, "preformatted", 0);

  fprintf(stderr, "fbreader - save settings...\n");

  FILE *f = iv_fopen(DataFile, "wb");
  if (f != NULL) {
	iv_fwrite(&docstate, 1, sizeof(tdocstate), f);
	iv_fclose(f);
  }

  bool bookFinished = false;
  if (position_mode == positionPage) {
	  if (! calc_in_progress) {
		int cpage = position_to_page(cpos);
		if (npages-cpage < 3 && cpage >= 5) {
			bookFinished = true;
		}
	}
  } else {
        int paraFull = bookview->model()->paragraphsNumber();
		ZLTextWordCursor cr = bookview->endCursor();
        if ((cr.paragraphCursor().index() + 1) >= paraFull) {
            bookFinished = true;
        }
  }
  if (bookFinished) {
	f = iv_fopen(ZetFile, "w");
	fclose(f);
  }

  CloseConfig(fcfg);

  fprintf(stderr, "fbreader - save settings done\n");

}

static void calc_timer() {

	fprintf(stderr, "calc_timer\n");

	int p, w, l;
	int p2, w2, l2;

	if (! calc_in_progress) {
		free(ballast);
		ballast = NULL;
		calc_orientation = GetOrientation();
//		mainApplication->refreshWindow();
		if (GetEventHandler() == main_handler) repaint(0);
		return;
	}

	if (is_footnote_mode()) {
		SetWeakTimer("CalcPages", calc_timer, 200);
		return;
	}

	lock_drawing = 1;
	current_position_changed = 1;

	if (calc_position_changed) {
		set_position(calc_current_position);
		calc_position_changed = 0;
	}

//***********************
#ifdef EMULATOR
long long prevpos = get_position();
unpack_position(prevpos, &p2, &w2, &l2);
#endif
//***********************

	mainApplication->doAction(ActionCode::LARGE_SCROLL_FORWARD);

	calc_current_position = get_position();
	if (calc_current_position == pagelist[calc_current_page-1]) {

		long long pos2 = calc_current_position;
                mainApplication->doAction(ActionCode::GOTO_NEXT_TOC_SECTION);
		calc_current_position = get_position();
		if (pos2 == calc_current_position) {

			pos2 = get_end_position();
			unpack_position(pos2, &p, &w, &l);
			set_position(pack_position(p+1, 0, 0));
			calc_current_position = get_position();
			if (calc_current_position <= pos2) {

				calc_in_progress = 0;
				set_position(cpos);
#ifndef EMULATOR
				SetHardTimer("CalcPages", calc_timer, 0);
#else
				SetHardTimer("CalcPages", calc_timer, 100);
#endif
				lock_drawing = 0;
				return;

			}

		}

	}


//***********************
//#ifdef EMULATOR
//unpack_position(calc_current_position, &p, &w, &l);
//if (p==p2) {
//	if (w == w2) {
//		fprintf(stderr, "1  %i,%i,%i\n", 0, 0, l-l2);
//	} else {
//		fprintf(stderr, "2  %i,%i,%i\n", 0, w-w2, l);
//	}
//} else {
//	fprintf(stderr, "3  %i,%i,%i\n", p-p2, w, l);
//}
//#endif
//***********************

	if (npages+4 >= pagelistsize) {
		pagelistsize += (pagelistsize >> 1);
fprintf(stderr, "plsize=%i\n", pagelistsize);
		pagelist = (long long *) realloc(pagelist, pagelistsize * sizeof(long long));
	}

	npages = ++calc_current_page;
	pagelist[npages-1] = calc_current_position;

	//int p, w, l;
	//unpack_position(calc_current_position, &p, &w, &l);
	//fprintf(stderr, "%i  %i %i %i\n", npages, p, w, l);

	SetHardTimer("CalcPages", calc_timer, 0 /*200*/);
	lock_drawing = 0;

}

static void calc_pages() {

	fprintf(stderr, "calc_pages\n");

	calc_in_progress = 1;
	calc_position_changed = 1;
	current_position_changed = 1;
	calc_current_page = npages = 1;
	calc_current_position = pack_position(0, 0, 0);
	calc_orientation = -1;

	ballast = malloc(32768);

	if (pagelist == NULL) {
		pagelistsize = 256;
		pagelist = (long long *) malloc(pagelistsize * sizeof(long long));
	}
	pagelist[0] = calc_current_position;
	SetHardTimer("CalcPages", calc_timer, 1);


}

static long long pack_position(int para, int word, int letter) {

	return ((long long)para << 40) | ((long long)word << 16) | ((long long)letter);

}

static void unpack_position(long long pos, int *para, int *word, int *letter) {

	*para = (pos >> 40) & 0xffffff;
	*word = (pos >> 16) & 0xffffff;
	*letter = pos & 0xffff;

}

static long long page_to_position(int page) {

	if (page < 1) page = 1;
	if (page > npages) page = npages;
	return pagelist[page-1];

}

static int position_to_page(long long position) {
	if (position_mode != positionPage)
		return 999999;

	int i;

	for (i=1; i<=npages; i++) {
		if (position < pagelist[i]){
		 return i;
		}
//		if (position <= pagelist[i-1]) return i;
	}
	return npages;

}

static void set_position(long long pos) {

	int para, word, letter;

	unpack_position(pos, &para, &word, &letter);
	bookview->gotoPosition(para, word, letter);
	calc_position_changed = 1;

}

static void restore_current_position() {

	if (current_position_changed) {
		set_position(cpos);
		current_position_changed = 0;
		calc_position_changed = 1;
	}

}

static void wait_to_calc(int page) {

	int hgshown=0;
	while (calc_in_progress && calc_current_page < page) {
		if (! hgshown) ShowHourglass();
		hgshown=1;
		calc_timer();
	}
//	ClearTimer(calc_timer);


}

static void select_page(int page) {
	if(stfile) stfile->writeOperation(SFO_OtherPage);

	int paraFull;

	switch (position_mode){
	case positionPage:
		wait_to_calc(page);
		cpos = page_to_position(page);
		break;
	case positionParagraph:
		if (page > 0) --page;
		paraFull = bookview->model()->paragraphsNumber();
		if (page >= paraFull) page=paraFull-1;
		if (page < 0) page=0;
		cpos = pack_position(page, 0, 0);
		break;
	case positionPercent:
	case positionPercentFloat:
		paraFull = bookview->model()->paragraphsNumber();
		page = paraFull * page / 100;
		if (page >= paraFull) page=paraFull-1;
		if (page < 0) page=0;
		cpos = pack_position(page, 0, 0);
		break;
	}
	set_position(cpos);
	calc_position_changed = 1;
	mainApplication->refreshWindow();
	clink = 0;
	repaint(1);

}


static void bmk_paint(int page, long long pos) {

	set_position(pos);
	mainApplication->refreshWindow();
	repaint(-1);
	set_position(cpos);

}

static void bmk_selected(int page, long long pos) {

	cpos = pos;
	set_position(pos);
	calc_position_changed = 1;
	mainApplication->refreshWindow();
	clink = 0;
	repaint(1);

}

static void bmk_added(int page, long long pos) {

	char buf[256];

/*
	if (page > 0 && page < 99999) {
		sprintf(buf, "%s %i", GetLangText("@Add_Bmk_P"), page);
	} else {
		sprintf(buf, "%s", GetLangText("@Add_Bmk_P"));
	}
	Message(ICON_INFORMATION, GetLangText("@Bookmarks"), buf, 1500);
*/
	draw_bmk_flag(1);

}

static void bmk_handler(int action, int page, long long pos) {

	switch (action) {
		case BMK_PAINT: bmk_paint(page, pos); break;
		case BMK_ADDED: bmk_added(page, pos); break;
		case BMK_SELECTED: bmk_selected(page, pos); break;
//		case BMK_CLOSED: repaint(0); break; //на прошивке 15.3 не отрисовывает текущую страницу
		case BMK_CLOSED: mainApplication->refreshWindow(); repaint(1); break;  // Костыль для прошивки 15.3
	}

}

static void open_bookmarks() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	int i;

	restore_current_position();
	if (is_footnote_mode()) mainApplication->doAction(ActionCode::CANCEL);
	for (i=0; i<docstate.nbmk; i++) {
		bmkpages[i] = calc_in_progress || (position_mode != positionPage) ? i+1 : position_to_page(docstate.bmk[i]);
	}
	long long bvpos = get_bookview_position();
	int bvpage = calc_in_progress || (position_mode != positionPage) ? -1 : position_to_page(bvpos);

	OpenBookmarks(bvpage, bvpos, bmkpages, docstate.bmk, &docstate.nbmk, 30, bmk_handler);

}

static void new_bookmark() {

	int i;

	restore_current_position();
	for (i=0; i<docstate.nbmk; i++) {
		bmkpages[i] = calc_in_progress || (position_mode != positionPage) ? i+1 : position_to_page(docstate.bmk[i]);
	}
	long long bvpos = get_bookview_position();
	int bvpage = calc_in_progress || (position_mode != positionPage) ? -1 : position_to_page(bvpos);
	SwitchBookmark(bvpage, bvpos, bmkpages, docstate.bmk, &docstate.nbmk, 30, bmk_handler);
	repaint(0);

}

static void toc_selected(long long position) {

	cpos = position;
	set_position(cpos);
	calc_position_changed = 1;
	
	if(hide_status_dialog_timeout) SetPanelType(0);

	mainApplication->refreshWindow();
	clink = 0;
	repaint(1);

}

static void open_contents() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	long long pos;
	char *p;
	int i;

	if (TOC) {
		for (i=0; i<toc_size; i++) free(TOC[i].text);
		free(TOC);
		TOC = NULL;
	}

	toc_size = xxx_myTOC.size();
	if (toc_size == 0) {
		Message(ICON_INFORMATION, "FBReader", localize("No_contents"), 2000);
		return;
	}

	int paraFull = bookview->model()->paragraphsNumber();
	TOC = (tocentry *) malloc((toc_size+1) * sizeof(tocentry));
	for (i=0; i<toc_size; i++) {
		TOC[i].level = xxx_myTOC[i].level;
		if (xxx_myTOC[i].paragraph == -1) {
			TOC[i].position = -1;
			TOC[i].page = 0;
		} else {
			pos = pack_position(xxx_myTOC[i].paragraph, 0, 0);
			TOC[i].position = pos;
			switch (position_mode) {
			case positionPage:
				TOC[i].page = calc_in_progress ? -1 : position_to_page(pos);
				break;
			case positionParagraph:
				TOC[i].page = xxx_myTOC[i].paragraph;
				break;
			case positionPercent:	// by default position will be in percent
			case positionPercentFloat:	// by default position will be in percent
			default:
				TOC[i].page = (paraFull > 0) ? ((xxx_myTOC[i].paragraph * 100) / paraFull) : 100;
				break;
			}
		}
		TOC[i].text = strdup((char *)(xxx_myTOC[i].text.c_str()));
		p = TOC[i].text;
		while (*p) {
			if (*p == '\r' || *p == '\n') *p = ' ';
			p++;
		}

		//int p, w, l;
		//unpack_position(pos, &p, &w, &l);
		//fprintf(stderr, "%i %i %i\n", p, w, l);
		//fprintf(stderr, "%llx %i %s\n", pos, TOC[i].page, TOC[i].text);
	}

	restore_current_position();
	pos = get_end_position();

	if(PanelHeight() == 0) SetPanelType(1);	// force show panel

	OpenContents(TOC, toc_size, pos, toc_selected);

}

static void open_notes_menu() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	bool bHidePanel = false;
	OpenNotesMenu(OriginalName, book_title, cpos);

}

static void configuration_updated() {

	clean_config_style_entries();
	SaveConfig(fcfg);
	save_curr_styles();		// save changed in current style
	restore_current_position();
	SetEventHandler(main_handler);
	apply_config(1, 1);

}

int human_size(char *buf, unsigned long long bsize, unsigned long long nblocks) {

	unsigned long long value = bsize * nblocks;
	int n;

	if (value < 1000LL) {
		n = snprintf(buf, 32, "%i %s", (int)value, localize("bytes"));
	} else if (value < 100000LL) {
		value = (value * 10LL) / 1024LL;
		n = snprintf(buf, 32, "%i.%i %s", (int)(value/10LL), (int)(value%10LL), localize("kb"));
	} else if (value < 1000000LL) {
		n = snprintf(buf, 32, "%i %s", (int)(value/1024LL), localize("kb"));
	} else if (value < 100000000LL) {
		value = (value * 10LL) / (1024LL * 1024LL);
		n = snprintf(buf, 32, "%i.%i %s", (int)(value/10LL), (int)(value%10LL), localize("mb"));
	} else {
		n = snprintf(buf, 32, "%i %s", (int)(value/(1024LL * 1024LL)), localize("mb"));
	}
	return n;

}

int human_date(char *buf, time_t t) {

	time_t tt = t;
	struct tm *ctm = localtime(&tt);

	return sprintf(buf, "%s  %02i:%02i", DateStr(t), ctm->tm_hour, ctm->tm_min);

}

static void show_about() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	char buf[2048], sbuf[128], *p;
	int i, n=0;
	int size = sizeof(buf)-1;

	p = strrchr(OriginalName, '/');
	n += snprintf(buf+n, size-n, "%s:  %s\n", localize("Filename"), p ? p+1 : OriginalName);

	if (bi->typedesc && bi->typedesc[0]) {
	  n += snprintf(buf+n, size-n, "%s:  %s\n", localize("Type"), GetLangText(bi->typedesc));
	}

	if (bi->title && bi->title[0]) {
	  n += snprintf(buf+n, size-n, "%s:  %s\n", localize("Title"), bi->title);
	}

	if (bi->author && bi->author[0]) {
	  n += snprintf(buf+n, size-n, "%s:  %s\n", localize("Author"), bi->author);
	}

	if (bi->series && bi->series[0]) {
	  n += snprintf(buf+n, size-n, "%s:  %s\n", localize("Series"), bi->series);
	}

	for (i=0; i<10; i++) {
	  if (bi->genre[i] == NULL) break;
	  if (bi->genre[i][0]) {
	    n += snprintf(buf+n, size-n, "%s:  %s\n", localize("Genre"), localize("genres",bi->genre[i]));
	  }
	}

	human_size(sbuf, 1, bi->size);
	n += snprintf(buf+n, size-n, "%s:  %s\n", localize("Size"), sbuf);

	human_date(sbuf, bi->ctime);
	n += snprintf(buf+n, size-n, "%s:  %s\n", localize("Written"), sbuf);

	n += snprintf(buf+n, size-n, "------------------------------------------------------\nFBReader, PocketBook version\n" MODDER_VERSION "\n");

	Message(ICON_INFORMATION, localize("About_book"), buf, 30000);

}

// This method just renames non-TTF fonts to names like: some_font{<original extension>}.ttf
// So these fonts become available to FBReader and setting dialog
static void reenable_nonttf_fonts() {
	ShowHourglass();

	DIR *dir_p;
	struct dirent *dp;
	int idx;
	char *msg = "No non-TTF fonts found.";
	std::list<std::string>	lFonts;

	// enumerate supported non-TTF fonts in user font directory
	dir_p = NULL;
	dir_p = opendir(USERFONTDIR);
	if(dir_p == NULL) {
		HideHourglass();
		Message(ICON_ERROR,"Error","Cannot enumerate fonts in user font directory: <PocketBook>/system/fonts\nOnly TTF fonts are available.",5000);
		return;
	}

	while((dp = readdir(dir_p)) != NULL) {
		idx = strlen(dp->d_name);
		if(idx <= 4) continue;

		idx -= 4;
		if(dp->d_name[idx] != '.') continue;	// only 3-symbol extensions allowed
		if(strncasecmp(dp->d_name + idx, ".TTF", 4)==0) continue;	// skip TTF fonts
		if(strncasecmp(dp->d_name + idx, ".AFM", 4)==0) continue;	// skip additional metric files

		lFonts.push_back(dp->d_name);
		fprintf(stderr,"Non-TTF font: %s\n",dp->d_name);
	}
	closedir(dir_p);

	// rename found fonts
	if(!lFonts.empty()) {
		msg = "There are several non-TTF fonts.\nThey were enabled for using with FBReader.\nPlease, close FBReader and start again to accomodate changes.";

/*		int	ret;
		std::string	cmd;
		std::list<std::string>::iterator	it,lim;
		for(it=lFonts.begin(),lim=lFonts.end();it!=lim;++it) {
			cmd = "cd " USERFONTDIR "; ln -s ";
			cmd += *it;
			cmd += " ";
			cmd += *it;
			cmd[cmd.length()-4] = '{';
			cmd += "}.ttf;";

			ret = system(cmd.c_str());
			fprintf(stderr,"Link: %s(result=%d)\n",cmd.c_str(),ret);
		}*/
		int	ret;
		std::string	src,tgt;
		std::list<std::string>::iterator	it,lim;
		for(it=lFonts.begin(),lim=lFonts.end();it!=lim;++it) {
			src = USERFONTDIR "/";
			src += *it;
			tgt = src;
			tgt[tgt.length()-4] = '{';
			tgt += "}.ttf";

			// remove old renamed file
			unlink(tgt.c_str());
			// rename to .ttf
			ret = rename(src.c_str(),tgt.c_str());
			fprintf(stderr,"Rename: %s\n->%s (result=%d)\n",src.c_str(),tgt.c_str(),ret);
		}
	}

	HideHourglass();
	Message(ICON_INFORMATION,"Re-enable non-TTF fonts",msg,10000);
}

// support class to store styles in file
class ZLTextStyleWriter : public ZLXMLWriter {

public:
	ZLTextStyleWriter(ZLOutputStream &out) : ZLXMLWriter(out) {}
	virtual ~ZLTextStyleWriter() {};

	void store(ZLTextStyleCollection &collection);

protected:
	void addAttributeBool(const char *attr,bool value);
	void addAttributeBool3(const char *attr,ZLBoolean3 value);
	void addAttributeInt(const char *attr,long value);
	void addAttributeIntNonZero(const char *attr,long value);

};

void ZLTextStyleWriter::store(ZLTextStyleCollection &collection) {
	addTag("DefaultStyles",false);
	// write base style
	addTag("base",true);
	ZLTextBaseStyle	&base = collection.baseStyle();
	addAttribute("family",base.FontFamilyOption.value());
	addAttributeInt("fontSize",base.FontSizeOption.value());
	// write styles
	const std::map<ZLTextKind,ZLTextStyleDecoration*>	&styles = collection.getDecorations();
	std::map<ZLTextKind,ZLTextStyleDecoration*>::const_iterator	it,lim;
	for(it=styles.begin(),lim=styles.end();it!=lim;++it) {
		ZLTextStyleDecoration	*style = it->second;
		if(!style) continue;
		addTag("style",true);
		// write base attributes
		addAttributeInt("id",it->first);
		addAttribute("name",style->name());
		addAttributeIntNonZero("fontSizeDelta",style->FontSizeDeltaOption.value());
		addAttributeBool3("bold",style->BoldOption.value());
		addAttributeBool3("italic",style->ItalicOption.value());
		addAttributeIntNonZero("vShift",style->VerticalShiftOption.value());
		addAttributeBool3("allowHyphenations",style->AllowHyphenationsOption.value());
		ZLTextStyleDecoration::HyperlinkStyle	hlink = style->hyperlinkStyle();
		if(hlink==ZLTextStyleDecoration::INTERNAL) addAttribute("hyperlink","internal");
		else if(hlink==ZLTextStyleDecoration::EXTERNAL) addAttribute("hyperlink","external");
		if(style->FontFamilyOption.value().length()>0)
			addAttribute("family",style->FontFamilyOption.value());
		// write extended attributes
		if(style->isFullDecoration()) {
			ZLTextFullStyleDecoration	*fullstyle = (ZLTextFullStyleDecoration*)style;
			addAttributeIntNonZero("spaceBefore",fullstyle->SpaceBeforeOption.value());
			addAttributeIntNonZero("spaceAfter",fullstyle->SpaceAfterOption.value());
			addAttributeIntNonZero("leftIndent",fullstyle->LeftIndentOption.value());
			addAttributeIntNonZero("rightIndent",fullstyle->RightIndentOption.value());
			addAttributeIntNonZero("firstLineIndentDelta",fullstyle->FirstLineIndentDeltaOption.value());
			double lineSpace = fullstyle->LineSpaceOption.value();
			if(lineSpace>0.0) addAttributeInt("lineSpacingPercent",(long)(lineSpace*100.0));
			ZLTextAlignmentType	alignment = (ZLTextAlignmentType)fullstyle->AlignmentOption.value();
			switch(alignment){
			case ALIGN_LEFT: addAttribute("alignment","left"); break;
			case ALIGN_RIGHT: addAttribute("alignment","right"); break;
			case ALIGN_CENTER: addAttribute("alignment","center"); break;
			case ALIGN_JUSTIFY: addAttribute("alignment","justify"); break;
			}
		}
		else addAttributeBool("partial",true);
	}
	closeTag();
	closeAllTags();
}

inline void ZLTextStyleWriter::addAttributeBool(const char *attr,bool value) {
	addAttribute(attr,(value?"true":"false"));
}

inline void ZLTextStyleWriter::addAttributeBool3(const char *attr,ZLBoolean3 value) {
	if(value!=B3_UNDEFINED) addAttribute(attr,(value==B3_TRUE?"true":"false"));
}

inline void ZLTextStyleWriter::addAttributeInt(const char *attr,long value) {
	char	buf[16];
	sprintf(buf,"%ld",value);
	addAttribute(attr,buf);
}

inline void ZLTextStyleWriter::addAttributeIntNonZero(const char *attr,long value) {
	if(value!=0) addAttributeInt(attr,value);
}

// test method to save styles to file
static void save_curr_styles() {
	std::string fullfilename = USERSTYLESDIR;
	fullfilename += "styles.xml";
	// save style to file
	ShowHourglass();
	shared_ptr<ZLOutputStream> 	out(((ZLNXFSManager&)ZLFSManager::instance()).createOutputStream(fullfilename));
	if(out.isNull()) {Message(ICON_ERROR,"Error","Cannot save style: wrong file name",10000); return;}
	if(!out->open()) {Message(ICON_ERROR,"Error","Cannot save style: cannot create file to save style",10000); return;}
	ZLTextStyleWriter	writer(*out);
	writer.store(ZLTextStyleCollection::instance());
	HideHourglass();
	// debug
	fprintf(stderr,"current style saved\n");
}

// load list of available styles into list
void load_list_of_styles() {
	// clear previous
	if(styles_list) delete[](styles_list);
	styles_list = NULL;
	style_names_list.clear();

	// enumerate style files
	DIR *dir_p;
	struct dirent *dp;
	int	pos;

	dir_p = opendir(USERSTYLESDIR);
	if(dir_p != NULL) {
		while((dp = readdir(dir_p)) != NULL) {
			pos = strlen(dp->d_name);
			if(pos<4) continue;
			pos -= 4;
			if(strcasecmp(dp->d_name+pos,".xml")!=0) continue;
			if(strcasecmp(dp->d_name,"styles.xml")==0) continue;
			style_names_list.push_back(dp->d_name);
		}
	}

	// prepare selection index
	int count = style_names_list.size() + 2;
	styles_list = new char*[count];
	styles_list[0] = "     ";
	for(int i=0;(i+2)<count;++i)
		styles_list[i+1] = const_cast<char*>(style_names_list[i].c_str());
	styles_list[count-1] = NULL;

	// set to menu
	styles_ce[1].variants = styles_list;
	styles_ce[1].deflt = "0";

}

// variables to receive entered text
char last_style_save_filename[48] = "";

static void fill_style_menu();

// keyboard handler for style save
void kbd_save_style_to_file(char *text) {
	if(text) {
		std::string	filename = text;
		if(!strchr(text,'.')) filename+=".xml";	// add extension
		std::string fullfilename = USERSTYLESDIR;
		fullfilename += filename;
		// check name
		if(strcasecmp(filename.c_str(),"styles.xml")==0) {Message(ICON_ERROR,"Error","Cannot save style: this file name is reserved for internal use",10000); return;}
		if(strcasecmp(filename.c_str(),"config.xml")==0) {Message(ICON_ERROR,"Error","Cannot save style: this file name is reserved for internal use",10000); return;}
		// save style to file
		ShowHourglass();
		shared_ptr<ZLOutputStream> 	out(((ZLNXFSManager&)ZLFSManager::instance()).createOutputStream(fullfilename));
		if(out.isNull()) {Message(ICON_ERROR,"Error","Cannot save style: wrong file name",10000); return;}
		if(!out->open()) {Message(ICON_ERROR,"Error","Cannot save style: cannot create file to save style",10000); return;}
		ZLTextStyleWriter	writer(*out);
		writer.store(ZLTextStyleCollection::instance());
		// reload style files
		load_list_of_styles();
		HideHourglass();
		// show notification
		std::string	msg = "Current style was saved successfully\nto file: ";
		msg += filename;
		Message(ICON_INFORMATION,"Success",(char*)msg.c_str(),10000);
	}
}

// method to save style to file
static void save_curr_style() {
	OpenKeyboard("Enter file name to save style",last_style_save_filename,45,/*KBD_LOWER*/0,kbd_save_style_to_file);
}

// method to load style from file
static void load_style() {
	// get and check file index
	int file_index = ReadInt(fcfg, "style_load", -1);
	if((file_index<1)||(file_index>=style_names_list.size())) return;
	--file_index;
	// copy selected file over styles.xml
	ShowHourglass();
#ifdef EMULATOR
	std::string	cmd = "/bin/rm -f " USERSTYLESDIR "styles.xml";
#else
	std::string	cmd = "rm -f " USERSTYLESDIR "styles.xml";
#endif
	system(cmd.c_str());
#ifdef EMULATOR
	cmd = "/bin/cp -fT " USERSTYLESDIR;
#else
	cmd = "cp -fT " USERSTYLESDIR;
#endif
	cmd += style_names_list[file_index];
	cmd += " " USERSTYLESDIR "styles.xml";
	system(cmd.c_str());
	// reload styles
//	ZLTextStyleCollection::deleteInstance();
//	ZLTextStyleCollection::instance();
	ZLTextStyleCollection::reloadStyles(style_names_list[file_index].c_str());
	// rebuild menu
	fill_style_menu();
	HideHourglass();
}

static void load_style_notify() {
	// get and check file index
	int file_index = ReadInt(fcfg, "style_load", -1);
	if((file_index<1)||(file_index>=style_names_list.size())) return;
	--file_index;
	// clear entry in config file
	DeleteInt(fcfg,"style_load");
	SaveConfig(fcfg);
	// set style chaged flag
	styles_has_changed = true;

	std::string	msg = "Loaded style ";
	msg += style_names_list[file_index];
	Message(ICON_INFORMATION,"Success",(char*)msg.c_str(),10000);
}

// method to load default style
static void load_style_default() {
	// delete styles.xml
	ShowHourglass();
#ifdef EMULATOR
	std::string	cmd = "/bin/rm -f " USERSTYLESDIR "styles.xml";
#else
	std::string	cmd = "rm -f " USERSTYLESDIR "styles.xml";
#endif
	system(cmd.c_str());
	// reload styles
	ZLTextStyleCollection::deleteInstance();
	ZLTextStyleCollection::instance();
	// rebuild menu
	fill_style_menu();
	HideHourglass();
	// clear entry in config file
	DeleteInt(fcfg,"style_load_default");
	// set style chaged flag
	styles_has_changed = true;

	std::string	msg = "Loaded default style";
	Message(ICON_INFORMATION,"Success",(char*)msg.c_str(),10000);
}

enum eStyleSubentries{
	eSS_font,
	eSS_font_default,
	eSS_bold,
	eSS_italic,
	eSS_vShift,
	eSS_allowHyphenations,
	eSS_hyperlink,
	eSS_spaceBefore,
	eSS_spaceAfter,
	eSS_leftIndent,
	eSS_rightIndent,
	eSS_firstLineIndentDelta,
	eSS_alignment,
	eSS_lineSpacingPercent,
};

std::string	__tmp_style_base_font_family;
int	__tmp_style_base_font_size=18;
char __tmp_style_buf[48];
const ZLResource *style_res = NULL;
const ZLResource *style_edit_res = NULL;

/// methods to construct style menu
static void addStyleTopMenuEntry(int pos,const char *text,int sub) {
	style_entries[pos].type = CFG_SUBMENU;
	style_entries[pos].icon = &ci_textfmt;
	style_entries[pos].text = localize(*style_res,text);
	sprintf(__tmp_style_buf,"style_edit_top_%.02d",pos);
	style_entry_name_strings.push_back(__tmp_style_buf);
	style_entries[pos].name = const_cast<char*>(style_entry_name_strings.back().c_str());
	style_entries[pos].deflt = NULL;
	style_entries[pos].variants = NULL;
	style_entries[pos].submenu = &style_entries[sub];
}

static void addStyleMenuEntry(int pos,int id,int type,const ibitmap *icon,const char *text,eStyleSubentries subentry,const char *def,char **var) {
	style_entries[pos].type = type;
	style_entries[pos].icon = icon;
	style_entries[pos].text = localize(*style_edit_res,text);
	sprintf(__tmp_style_buf,"style_edit_item_%.05d",id*100+subentry);
	style_entry_name_strings.push_back(__tmp_style_buf);
	style_entries[pos].name = const_cast<char*>(style_entry_name_strings.back().c_str());
	if(type==CFG_FONT) {
		// add additional font entry names for correct clearing
		int len = strlen(__tmp_style_buf);
		strcpy(__tmp_style_buf+len,".b");
		style_entry_name_strings.push_back(__tmp_style_buf);
		strcpy(__tmp_style_buf+len,".bi");
		style_entry_name_strings.push_back(__tmp_style_buf);
		strcpy(__tmp_style_buf+len,".i");
		style_entry_name_strings.push_back(__tmp_style_buf);
	}
	if(def) {
		style_entry_strings.push_back(def);
		style_entries[pos].deflt = const_cast<char*>(style_entry_strings.back().c_str());
	}
	style_entries[pos].variants = var;
	style_entries[pos].submenu = NULL;
}

static void addStyleFontMenuEntry(int pos,int id,const std::string &family,int sizeDelta) {
	std::string	s1;
	char	buf[16];
	if(family.empty()) s1=__tmp_style_base_font_family;
	else s1=family;
	sprintf(buf,",%d",__tmp_style_base_font_size+sizeDelta);
	s1 += buf;
//	fprintf(stderr,"Style font: %s -> %s\n",family.c_str(),s1.c_str());
	addStyleMenuEntry(pos,id,CFG_FONT,&ci_font,"Font",eSS_font,s1.c_str(),NULL);
}

static void addStyleBoolMenuEntry(int pos,int id,const ibitmap *icon,const char *text,eStyleSubentries subentry,ZLBoolean3 val) {
	addStyleMenuEntry(pos,id,CFG_INDEX,icon,text,subentry,(val==B3_UNDEFINED?"0":(val==B3_TRUE?"1":"2")),style_bool_variants);
}

static void addStyleIntMenuEntry(int pos,int id,const ibitmap *icon,const char *text,eStyleSubentries subentry,long val) {
	char	buf[16];
	sprintf(buf,"%ld",val);
	addStyleMenuEntry(pos,id,CFG_NUMBER,icon,text,subentry,buf,NULL);
}

static void addStyleHLinkMenuEntry(int pos,int id,ZLTextStyleDecoration::HyperlinkStyle val) {
	char	*def = "0";
	switch(val){
	case ZLTextStyleDecoration::INTERNAL: def="1"; break;
	case ZLTextStyleDecoration::EXTERNAL: def="2"; break;
	}
	addStyleMenuEntry(pos,id,CFG_INDEX,&ci_textfmt,"HyperlinkType",eSS_hyperlink,def,style_hlink_variants);
}

static void addStyleAlignMenuEntry(int pos,int id,long val) {
	char	*def = "0";
	switch(val){
	case ALIGN_LEFT: def="1"; break;
	case ALIGN_RIGHT: def="2"; break;
	case ALIGN_CENTER: def="3"; break;
	case ALIGN_JUSTIFY: def="4"; break;
	}
	addStyleMenuEntry(pos,id,CFG_INDEX,&ci_textfmt,"TextAlignment",eSS_alignment,def,style_align_variants);
}

static bool bLocalizedStyleVariants = false;

static void localize_style_edit_variants() {
	if(bLocalizedStyleVariants) return;
	const ZLResource &res = ZLResource::resource("edit_style_values");
	apply_localization2variants(style_bool_variants,res);
	apply_localization2variants(style_hlink_variants,res);
	apply_localization2variants(style_align_variants,res);
	bLocalizedStyleVariants = true;
}

static void fill_style_menu() {
	// count required entries
	int req_count = 2;	// required entries count: base->family/base->fonSize + <end>
	int top_entries = 2;	// top entries count: base + <end>
	int free_pos = 0;	// position of first free entry
	int top_pos = 0;	// position of first free top entry
	ZLTextStyleCollection &collection = ZLTextStyleCollection::instance();
	const std::map<ZLTextKind,ZLTextStyleDecoration*>	&styles = collection.getDecorations();
	std::map<ZLTextKind,ZLTextStyleDecoration*>::const_iterator	it,lim;
	for(it=styles.begin(),lim=styles.end();it!=lim;++it) {
		ZLTextStyleDecoration	*style = it->second;
		if(!style) continue;
		++top_entries;
		req_count += 9;	// name + <attributes...> + <use base font> - <fontSizeDelta> + <end>
		if(style->isFullDecoration())
			req_count += 7;	// <ext.attributes...>
	}
	// debug
	fprintf(stderr,"required %d menu entries\n",req_count);

	// allocate
	if(style_entry_count<req_count) {
		if(style_entries) delete[](style_entries);
		style_entries = new iconfigedit[req_count];
		if(!style_entries) {
			fprintf(stderr,"Cannot allocate entries for style menu - fatal\n");
			style_entry_count = 0;
			styles_ce[3].type = CFG_INFO;
			styles_ce[3].submenu = NULL;
			return;
		}
		style_entry_count = req_count;
	}
	memset(style_entries,0,req_count*sizeof(iconfigedit));
	style_entry_name_strings.clear();
	style_entry_strings.clear();

	// localize value variants
	localize_style_edit_variants();

	// fill menu
	free_pos = top_entries;
	top_pos = 0;

	// base font
	char	buf[64];
	std::string	s1;
	ZLTextBaseStyle	&base = collection.baseStyle();
/*	style_entries[top_pos].type = CFG_FONT;
	style_entries[top_pos].icon = &ci_font;
	style_entries[top_pos].text = "Base font";
//	style_entries[top_pos].name = "style_edit_base_font";
	style_entries[top_pos].name = "font";	// use
*/	__tmp_style_base_font_family = base.FontFamilyOption.value();
//	s1 = __tmp_style_base_font_family;
	__tmp_style_base_font_size = base.FontSizeOption.value();
/*	sprintf(buf,",%d",__tmp_style_base_font_size);
	s1 += buf;
	style_entry_strings.push_back(s1);
	style_entries[top_pos].deflt = const_cast<char*>(style_entry_strings.back().c_str());
	style_entries[top_pos].variants = NULL;
	style_entries[top_pos].submenu = NULL;
	++top_pos;
*/
	// localization
	style_res = &ZLResource::resource("style");
	style_edit_res = &ZLResource::resource("edit_style");

	// styles
	for(it=styles.begin(),lim=styles.end();it!=lim;++it) {
		ZLTextStyleDecoration	*style = it->second;
		if(!style) continue;
		int id = it->first;
		fprintf(stderr,"style %d\n",id);
		addStyleTopMenuEntry(top_pos++,style->name().c_str(),free_pos);
		addStyleFontMenuEntry(free_pos++,id,style->FontFamilyOption.value(),style->FontSizeDeltaOption.value());
		addStyleMenuEntry(free_pos++,id,CFG_INFO,&ci_font,"SetBaseFont",eSS_font_default,NULL,NULL);
		addStyleBoolMenuEntry(free_pos++,id,&ci_textfmt,"Bold",eSS_bold,style->BoldOption.value());
		addStyleBoolMenuEntry(free_pos++,id,&ci_textfmt,"Italic",eSS_italic,style->ItalicOption.value());
		addStyleIntMenuEntry(free_pos++,id,&ci_textfmt,"VerticalDisplacement",eSS_vShift,style->VerticalShiftOption.value());
		addStyleBoolMenuEntry(free_pos++,id,&ci_hyphen,"AllowHyphenation",eSS_allowHyphenations,style->AllowHyphenationsOption.value());
		addStyleHLinkMenuEntry(free_pos++,id,style->hyperlinkStyle());
		if(style->isFullDecoration()) {
			ZLTextFullStyleDecoration	*fullstyle = (ZLTextFullStyleDecoration*)style;
			addStyleIntMenuEntry(free_pos++,id,&ci_spacing,"SpaceBefore",eSS_spaceBefore,fullstyle->SpaceBeforeOption.value());
			addStyleIntMenuEntry(free_pos++,id,&ci_spacing,"SpaceAfter",eSS_spaceAfter,fullstyle->SpaceAfterOption.value());
			addStyleIntMenuEntry(free_pos++,id,&ci_margins,"LeftIndent",eSS_leftIndent,fullstyle->LeftIndentOption.value());
			addStyleIntMenuEntry(free_pos++,id,&ci_margins,"RightIndent",eSS_rightIndent,fullstyle->RightIndentOption.value());
			addStyleIntMenuEntry(free_pos++,id,&ci_margins,"FirstLineIndentDelta",eSS_firstLineIndentDelta,fullstyle->FirstLineIndentDeltaOption.value());
			addStyleAlignMenuEntry(free_pos++,id,fullstyle->AlignmentOption.value());
			double lineSpace = fullstyle->LineSpaceOption.value();
			long lineSpacePC = 0;
			if(lineSpace>0.0) lineSpacePC=(long)(lineSpace*100.0);
			addStyleIntMenuEntry(free_pos++,id,&ci_spacing,"Linespacing",eSS_lineSpacingPercent,lineSpacePC);
		}
		++free_pos;
	}

	// set submenu
	styles_ce[3].type = CFG_SUBMENU;
	styles_ce[3].submenu = style_entries;

	fprintf(stderr,"style menu is ready\n");
}

// clear dummy entries from config
static void clean_config_style_entries() {
	std::list<std::string>::iterator	it,lim;
	for(it=style_entry_name_strings.begin(),lim=style_entry_name_strings.end();it!=lim;++it)
		DeleteString(fcfg,const_cast<char*>(it->c_str()));
	DeleteString(fcfg,"style_load");
}

/// methods to apply style changes
static void apply_style_base_font() {

}

static void apply_style_entry_font(ZLTextStyleDecoration *style,const char *value) {
	if(!value) return;
	// parse font
	std::string family = value;
	std::string sz;
	size_t	pos = family.find(',');
	if(pos!=std::string::npos) {
		sz = family.substr(pos+1);
		family = family.substr(0,pos);
	}
	// check and set
	if(family.compare(__tmp_style_base_font_family)==0) style->FontFamilyOption.setValue("");
	else {
		ifont *f = OpenFont(const_cast<char*>(family.c_str()), 18, 1);
		style->FontFamilyOption.setValue(f->family);
	}
	if(!sz.empty()) {
		int font_sz = atoi(sz.c_str());
		font_sz -= __tmp_style_base_font_size;
		style->FontSizeDeltaOption.setValue(font_sz);
	}
}

static ZLBoolean3 index2bool(const char *val) {
	if(!val) return B3_UNDEFINED;
	if(*val=='1') return B3_TRUE;
	if(*val=='2') return B3_FALSE;
	return B3_UNDEFINED;
}

static void apply_int_option(ZLIntegerOption &opt,const char *val) {
	if(!val) return;
	if((*val<'0')||(*val>'9')) {
		Message(ICON_ERROR,"Error","Wrong value: number expected",10000);
		return;
	}
	int v = atoi(val);
	opt.setValue(v);
}

static void apply_range_option(ZLIntegerRangeOption &opt,const char *val) {
	if(!val) return;
	if((*val<'0')||(*val>'9')) {
		Message(ICON_ERROR,"Error","Wrong value: number expected",10000);
		return;
	}
	int v = atoi(val);
	opt.setValue(v);
}

static void apply_hlink_option(ZLTextStyleDecoration *style,const char *val) {
	if(!val) return;
	ZLTextStyleDecoration::HyperlinkStyle	st = ZLTextStyleDecoration::NONE;
	if(*val=='1') st=ZLTextStyleDecoration::INTERNAL;
	else if(*val=='2') st=ZLTextStyleDecoration::EXTERNAL;
	style->setHyperlinkStyle(st);
}

static void apply_align_option(ZLTextFullStyleDecoration *fullstyle,const char *val) {
	if(!val) return;
	int align = ALIGN_UNDEFINED;
	if(*val=='1') align=ALIGN_LEFT;
	else if(*val=='2') align=ALIGN_RIGHT;
	else if(*val=='3') align=ALIGN_CENTER;
	else if(*val=='4') align=ALIGN_JUSTIFY;
	fullstyle->AlignmentOption.setValue(align);
}

static void apply_style_entry(int id,int subentry,const char *value) {
	// get decoration object
	ZLTextStyleCollection &collection = ZLTextStyleCollection::instance();
	ZLTextStyleDecoration *style = collection.decoration(id);
	if(!style) {fprintf(stderr,"There is no style %d\n",id); return;}
	ZLTextFullStyleDecoration *fullstyle = (ZLTextFullStyleDecoration*)style;
	// set value
	switch(subentry){
	case eSS_font: apply_style_entry_font(style,value); break;
	case eSS_font_default: style->FontFamilyOption.setValue(""); break;
	case eSS_bold: style->BoldOption.setValue(index2bool(value)); break;
	case eSS_italic: style->ItalicOption.setValue(index2bool(value)); break;
	case eSS_vShift: apply_int_option(style->VerticalShiftOption,value); break;
	case eSS_allowHyphenations: style->AllowHyphenationsOption.setValue(index2bool(value)); break;
	case eSS_hyperlink: apply_hlink_option(style,value); break;
	case eSS_spaceBefore: apply_range_option(fullstyle->SpaceBeforeOption,value); break;
	case eSS_spaceAfter: apply_range_option(fullstyle->SpaceAfterOption,value); break;
	case eSS_leftIndent: apply_range_option(fullstyle->LeftIndentOption,value); break;
	case eSS_rightIndent: apply_range_option(fullstyle->RightIndentOption,value); break;
	case eSS_firstLineIndentDelta: apply_range_option(fullstyle->FirstLineIndentDeltaOption,value); break;
	case eSS_alignment: apply_align_option(fullstyle,value); break;
	case eSS_lineSpacingPercent:
		{
			if(!value) break;
			if((*value<'0')||(*value>'9')) {
				Message(ICON_ERROR,"Error","Wrong value: number expected",10000);
				break;
			}
			int v = atoi(value);
			double	v1 = v;
			if(v<=0) v1=0.0;
			v1 = v1 / 100.0;
			fullstyle->LineSpaceOption.setValue(v1);
		}
		break;
	}
}

static void apply_style_item(char *name) {
	if(strcmp(name,"style_edit_base_font")==0) apply_style_base_font();
	else if(strncmp(name,"style_edit_item_",16)==0) {
		int id = atoi(name+16);
		int subentry = id % 100;
		id /= 100;
		apply_style_entry(id,subentry,ReadString(fcfg,name,""));
	}
}

static void cfg_item_changed(char *name) {

	if (strcmp(name, "about") == 0) show_about();
	else if (strcmp(name, "font_reenable") == 0) reenable_nonttf_fonts();
//	else if (strcmp(name, "styles") == 0) save_curr_styles();
	else if (strcmp(name, "styles") == 0) styles_has_changed=true;
	else if (strcmp(name, "style_save") == 0) save_curr_style();
	else if (strcmp(name, "-style_load") == 0) load_style();
	else if (strcmp(name, "style_load") == 0) load_style_notify();
	else if (strcmp(name, "style_load_default") == 0) load_style_default();
	else if (strncmp(name, "style_edit_", 11) == 0) apply_style_item(name);

}

static void invert_current_link() {

	hlink *cl = &(links[clink]);
	InvertAreaBW(cl->x, cl->y, cl->w, cl->h);

}

static void end_link_mode() {

	int i;
	links_mode = 0;
	for (i=0; i<nlinks; i++) {
		free(links[i].href);
		links[i].href = 0;
	}

}

static void jump_ref(char *ref) {

	if (is_footnote_mode()) {
		mainApplication->doAction(ActionCode::CANCEL);
	}
	if (ref[0] == '=') {
		cpos = strtoll(ref+1, NULL, 0);
	} else {
		((FBReader *)mainApplication)->tryShowFootnoteView(ref, false);
		cpos = get_position();
	}
	set_position(cpos);

}

static void open_int_ext_link(char *href) {

	char buf[1024], *p, *pp;

	fprintf(stderr, "%s\n", href);

	strcpy(buf, href);
	p = strchr(buf, '#');
	if (p != NULL) {
		*(p++) = 0;
		if (strcasecmp(buf, FileName) == 0) {
			jump_ref(p);
			mainApplication->refreshWindow();
			return;
		}
	}

	if (strncasecmp(href, "http:", 5) == 0 ||
	    strncasecmp(href, "ftp:", 4) == 0 ||
	    strncasecmp(href, "mailto:", 7) == 0
	) {
		char local[1024];
		// build local file name to save link as .url
		buf[0]=0;
		strncat(buf,href,1023);
		int l=strlen(buf);
		if (l>0 && buf[l-1]=='/')
			buf[l-1]=0;
		else
			l=0;
		p=strrchr(buf,'/');
                if (p)
			p++;
		else
			p=buf;
		strcpy(local,"/mnt/ext1/system/links");
		mkdir(local,0777);
		strcat(local,"/");
		strncat(local,p,1023);
		strncat(local,".url",1023);
		FILE* file=fopen(local,"w");
		if (file) {
			strcpy(buf,localize("LinkSaved"));
			strncat(buf,local+9,1023);  //leave out internal mount dir
			Message(ICON_INFORMATION, "FBReader", buf, 3000);
			strcpy(local,"[InternetShortcut]\r\nURL=");
			strncat(local,href,1023);
			fwrite(local,1,strlen(local),file);
			fclose(file);
		} else
			Message(ICON_WARNING, "FBReader", localize("Is_ext_link"), 2000);
		return;
	} else if (href[0] == '/') {
		pp = strrchr(href, '/') + 1;
	} else {
		pp = href;
	}
	strcpy(buf, OriginalName);
	p = strrchr(buf, '/');
	if (p == NULL) p = buf; else p++;
	strcpy(p, pp);
	pp = strchr(p, '#');
	if (pp != NULL) {
		*(pp++) = 0;
	}
	ShowHourglass();
	OpenBook(buf, pp, 1);

}

static void process_history_entry(int num)
{
	FILE *f;

	if (link_back >= 0 && num == 0) {
		iv_truncate(HISTORYFILE, link_back);
	} else {
		f = iv_fopen(HISTORYFILE, "a");
		if (f != NULL) {
			fprintf(f, "%s#=%lld\n", OriginalName, get_position());
			iv_fclose(f);
		}
	}
}

static void process_link(int num) {

	hlink *cl = &(links[num]);

	restore_current_position();

	if (cl->kind == 15) { // int. hyperlink

		process_history_entry(num);
		if (is_footnote_mode()) {
				mainApplication->doAction(ActionCode::CANCEL);
		}
		((FBReader *)mainApplication)->tryShowFootnoteView(cl->href, false);

	} else if (cl->kind == 16) { // footnote

		/// try to show footnote in baloon
		if(footnote_mode>0)
		{
			fprintf(stderr, "footnote %s\n", cl->href);

			BookModel	*pBM = ((FBReader *)mainApplication)->getBookModel();
			if(pBM){
				shared_ptr<ZLTextModel>	pFN(pBM->findFootNote(cl->href));
				if(!pFN.isNull()){
					bool	bValid=true;
					size_t	i,lim,str_size=0;
					ZLTextKind	iTextKind=17;
					std::string	sText,sTitle;
					ZLTextParagraph	*pPara;
					unsigned int	iControlCount=0;	///< number of control entries after last text one
					// walk through paragraphs
					for(i=0,lim=pFN->paragraphsNumber();(i<lim)&&bValid;++i){
						pPara = (*pFN)[i];
						if(!pPara) continue;
						if(pPara->kind()!=ZLTextParagraph::TEXT_PARAGRAPH) {fprintf(stderr,"no text paragraph\n");bValid=false;break;}
						// walk through paragraph entries
						ZLTextParagraph::Iterator	itPE(*pPara);
						for(;!itPE.isEnd();itPE.next()){
							if(itPE.entryKind()==ZLTextParagraphEntry::TEXT_ENTRY){
								const shared_ptr<ZLTextParagraphEntry>	pEntry(itPE.entry());
								if((str_size+((ZLTextEntry*)&(*pEntry))->dataLength())>2048/*1024*//*768*/) {fprintf(stderr,"too long footnote, at least %lu\n",(unsigned long)(str_size+((ZLTextEntry*)&(*pEntry))->dataLength()));bValid=false;break;}
								if(iTextKind==1){
									// title
									if(sTitle.empty()){
										// set title
										sTitle.append(((ZLTextEntry*)&(*pEntry))->data(),((ZLTextEntry*)&(*pEntry))->dataLength());
									} else {
										// add to text
										sText += "\n\n";
										sText.append(((ZLTextEntry*)&(*pEntry))->data(),((ZLTextEntry*)&(*pEntry))->dataLength());
										sText += "\n";
										str_size += ((ZLTextEntry*)&(*pEntry))->dataLength()+3;
									}
								} else {
									// normal text
//									if((!sText.empty())&&(iControlCount>0)){
//										// add new line if style was changed
//										sText += "\n";
//										++str_size;
//									}
									iControlCount = 0;
									sText.append(((ZLTextEntry*)&(*pEntry))->data(),((ZLTextEntry*)&(*pEntry))->dataLength());
									str_size += ((ZLTextEntry*)&(*pEntry))->dataLength();
								}
							}
							else if(itPE.entryKind()==ZLTextParagraphEntry::FIXED_HSPACE_ENTRY){
								sText += "\n";
								++str_size;
							}
							else if(itPE.entryKind()==ZLTextParagraphEntry::CONTROL_ENTRY){
								const shared_ptr<ZLTextParagraphEntry>	pEntry(itPE.entry());
								iTextKind = ((ZLTextControlEntry*)&(*pEntry))->kind();
								if(((ZLTextControlEntry*)&(*pEntry))->isStart())
									++iControlCount;
//								fprintf(stderr,"control entry, kind %d\n",(int)((ZLTextControlEntry*)&(*pEntry))->kind());
							}
							else {fprintf(stderr,"no text entry %d\n",(int)itPE.entryKind());/*bValid=false;break;*/}
						}
					}
					// display baloon if acceptable
//					bValid = false;
					if(bValid){
						if(sTitle.empty()) sTitle=localize("Footnote");
						fprintf(stderr,"footnote length %lu\n",(unsigned long)str_size);
						Message(0,const_cast<char*>(sTitle.c_str()),const_cast<char*>(sText.c_str()),32000);
						invert_current_link();
						if (clink == nlinks - 1) { // not exit from link_mode if many footnote on the page
							end_link_mode();
							SetEventHandler(main_handler);
						}
						return;
					}
				}
			}
		}

		process_history_entry(num);
		((FBReader *)mainApplication)->tryShowFootnoteView(cl->href, false);

	} else if (cl->kind == 37) { // ext. hyperlink

		process_history_entry(num);
		open_int_ext_link(cl->href);

	} else { // default: just process history

		process_history_entry(num);

	}

	end_link_mode();
	SetEventHandler(main_handler);

}


static int links_handler(int type, int par1, int par2) {

	hlink *cl = &(links[clink]);
	int y0a, y0b, y1a, y1b;

	switch (type) {

		case EVT_SHOW:
			repaint(0);
			break;

		case EVT_KEYPRESS:
		case EVT_KEYREPEAT:
			invert_current_link();
			y0a = cl->y;
			y1a = cl->y+cl->h+1;
			switch (par1) {

				case KEY_UP:
					clink--;
					if (type == EVT_KEYREPEAT) clink = 0;
					if (clink < 0) goto lk_exit;
					goto lk_update;
				case KEY_RIGHT:
				case KEY_DOWN:
					clink++;
					if (type == EVT_KEYREPEAT) clink = nlinks-1;
					if (clink >= nlinks) clink = 0;
				lk_update:
					y0b = links[clink].y;
					y1b = links[clink].y+links[clink].h+1;
					if (y0b < y0a) y0a = y0b;
					if (y1b > y1a) y1a = y1b;
					invert_current_link();
					PartialUpdateBW(0, y0a, ScreenWidth(), y1a-y0a);
					break;

				case KEY_LEFT:
				case KEY_BACK:
				lk_exit:
					end_link_mode();
					soft_update = 1;
					SetEventHandler(main_handler);
					break;

				case KEY_OK:
					process_link(clink);
					break;
				
				default :
					invert_current_link();
			}

	}
	return 0;

}

static void cyclic_show_link() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	int i;

	nlinks = 0;

	for (i=0; i<xxx_page_links.size(); i++) {
		xxx_link cl = xxx_page_links.at(i);
		links[nlinks].x = imgposx+cl.x1;
		links[nlinks].y = imgposy+cl.y1;
		links[nlinks].w = cl.x2-cl.x1+1;
		links[nlinks].h = cl.y2-cl.y1+1;
		links[nlinks].kind = cl.kind;
		links[nlinks].href = strdup(cl.id.c_str());
		nlinks++;
		if (nlinks >= MAXLINKS) break;
	}

	if (nlinks == 0) {
		Message(ICON_INFORMATION, "FBReader", localize("No_links"), 2000);
		return;
	}

	reading_mode_off(false);

	if (clink >= nlinks)
		clink = 0;

	process_link(clink++);

	reading_mode_on();
}

static char *get_backlink() {

	static char buf[1024], *p;
	FILE *f;
	int fp=0, cfp=0;

	link_back = -1;
	buf[0] = 0;
	f = iv_fopen(HISTORYFILE, "r");
	if (f != NULL) {
		while (1) {
			cfp = iv_ftell(f);
			if (iv_fgets(buf, sizeof(buf)-1, f) == NULL || (buf[0] & 0xe0) == 0) break;
			fp = cfp;
		}
		iv_fseek(f, fp, SEEK_SET);
		iv_fgets(buf, sizeof(buf)-1, f);
		iv_fclose(f);
		p = buf + strlen(buf);
		while (p > buf && (*(p-1) & 0xe0) == 0) *(--p) = 0;
	}

	if (buf[0] != 0) {
		link_back = fp;
		return buf;
	}
	return NULL;

}

static void back_link() {

	char *s = get_backlink();
	if (s != NULL) {
		nlinks = 1;
		links[0].kind = 37; // external link
		links[0].href = strdup(s);
		process_link(0);
	}
	else if ((!links_mode) && footnote_ext) {
		cyclic_show_link();
	}

}

static void open_links() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	int i;
	char *s;

	nlinks = 0;

	s = get_backlink();
	if (s != NULL) {
		links[nlinks].x = 2;
		links[nlinks].y = 2;
		links[nlinks].w = 30;
		links[nlinks].h = 14;
		links[nlinks].kind = 37; // external link
		links[nlinks].href = strdup(s);
		nlinks++;
	}

	for (i=0; i<xxx_page_links.size(); i++) {
		xxx_link cl = xxx_page_links.at(i);
		links[nlinks].x = imgposx+cl.x1;
		links[nlinks].y = imgposy+cl.y1;
		links[nlinks].w = cl.x2-cl.x1+1;
		links[nlinks].h = cl.y2-cl.y1+1;
		links[nlinks].kind = cl.kind;
		links[nlinks].href = strdup(cl.id.c_str());
		nlinks++;
		if (nlinks >= MAXLINKS) break;
	}

	if (nlinks == 0) {
		Message(ICON_INFORMATION, "FBReader", localize("No_links"), 2000);
		return;
	}

	reading_mode_off(false);

	links_mode = 1;
	clink = 0;
	invert_current_link();
	SetEventHandler(links_handler);

}

static void open_dictionary() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	reading_mode_off();
	
	OpenDictionaryView(xxx_wordlist, NULL);
}

static void adjust_position() {

/*
	int cpage = position_to_page(get_position());
	cpos = page_to_position(cpage);
*/

}

static int search_handler(int type, int par1, int par2) {

	long long xpos;

	if (type == EVT_SHOW) {
		//out_page(0);
	}

	if (IsRTL() && ISKEYEVENT(type)) {
		if (par1 == KEY_LEFT) {
			par1 = KEY_RIGHT;
		} else if (par1 == KEY_RIGHT) {
			par1 = KEY_LEFT;
		}
	}

	if (type == EVT_KEYPRESS) {

		if (par1 == KEY_OK || par1 == KEY_BACK) {
			search_mode = 0;
			shared_ptr<ZLTextModel> model = bookview->model();
			model->removeAllMarks();
			SetEventHandler(main_handler);
		}
		if (par1 == KEY_LEFT) {
			restore_current_position();
			mainApplication->refreshWindow();
			xpos = cpos;
			ShowHourglass();
			bookview->findPrevious();
			cpos = get_position();
			if (cpos != xpos) {
				adjust_position();
				mainApplication->refreshWindow();
				repaint(1);
			} else {
				Message(ICON_INFORMATION, localize("Search"), localize("No_more_matches"), 2000);
			}
		}
		if (par1 == KEY_RIGHT) {
			restore_current_position();
			mainApplication->refreshWindow();
			xpos = cpos;
			ShowHourglass();
			bookview->findNext();
			cpos = get_position();
			if (cpos != xpos) {
				adjust_position();
				mainApplication->refreshWindow();
				repaint(1);
			} else {
				Message(ICON_INFORMATION, localize("Search"), localize("No_more_matches"), 2000);
			}
		}

	}

	return 0;

}

static void search_enter(char *text) {

	if (text == NULL || text[0] == 0) return;
	std::string s = text;

	restore_current_position();
	mainApplication->refreshWindow();
	SetEventHandler(search_handler);
	search_mode = 1;
	ShowHourglass();
	bookview->search(s, true, false, false, false);
	adjust_position();
	notinvertedpages = 32767;
	repaint(1);

}

static void open_quickmenu() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	reading_mode_off();
	OpenMenu3x3(m3x3, (const char **)strings3x3, menu_handler);
}
static void jump_pr10() { jump_pages(-10); }
static void jump_nx10() { jump_pages(+10); }
static void stop_jump() {
	if (invertoption==3 || ! calc_in_progress) {
		mainApplication->refreshWindow();
		repaint(1);
	}
}
static void open_pageselector() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	reading_mode_off();
	OpenPageSelector(select_page);
}
static void first_page() { select_page(1); }
static void last_page() { select_page(999999); }
//static void new_note() { CreateNote(OriginalName, book_title, get_bookview_position()); }
static void new_note() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	reading_mode_off();

	ibitmap *bm1=NULL, *bm2=NULL;
	bm1 = BitmapFromScreen(0, 0, ScreenWidth(), ScreenHeight()-PanelHeight());
	if (one_page_forward(-1)) {
		bm2 = BitmapFromScreen(0, 0, ScreenWidth(), ScreenHeight()-PanelHeight());
		one_page_back(-1);
	}
	CreateNoteFromImages(OriginalName, book_title, get_bookview_position(), bm1, bm2);
}
static void save_page_note() { CreateNoteFromPage(OriginalName, book_title, get_bookview_position()); notinvertedpages = 32767; }
static void open_notes() { 	if(stfile) stfile->writeOperation(SFO_Interface); reading_mode_off(); OpenNotepad(NULL); }
static void start_search() { 	if(stfile) stfile->writeOperation(SFO_Interface); reading_mode_off(); OpenKeyboard(localize("Search"), kbdbuffer, 30, 0, search_enter); }
static void font_bigger() { font_change(0+font_change_step); }
static void font_smaller() { font_change(0-font_change_step); }
static void open_rotate() { 	if(stfile) stfile->writeOperation(SFO_Interface); reading_mode_off(); OpenRotateBox(rotate_handler); }
static void main_menu() { 	if(stfile) stfile->writeOperation(SFO_Interface); OpenMainMenu(); }
static void exit_reader() { 	if(stfile) stfile->writeOperation(SFO_Interface); CloseApp(); }
static void open_mp3() { 	if(stfile) stfile->writeOperation(SFO_Interface); reading_mode_off(); OpenPlayer(); }
static void mp3_pause() { TogglePlaying(); }
static void volume_up() { int r = GetVolume(); SetVolume(r+3); }
static void volume_down() { int r = GetVolume(); SetVolume(r-3); }
static void open_config() {
	if(stfile) stfile->writeOperation(SFO_Interface);

	reading_mode_off();
	
	strcpy(autostr,"auto");
	if (detectedlang[0]) {
		strcat(autostr," (");
		strcat(autostr,detectedlang);
		strcat(autostr,")");
	}
	notinvertedpages = 32767;	// force redraw
	if(PanelHeight() == 0) SetPanelType(1);	// force show panel

	// localize menu
	apply_localization2config();

	// prepare style menu
	fill_style_menu();

	OpenConfigEditor(localize("FB_config"), fcfg, fbreader_ce, configuration_updated, cfg_item_changed);
}

static void switch_autoflip() {
	if((autopageflip>0) && autoflip_interface_on) {// do switching only of enabled and no interface shown
		if(autoflip_enabled) {
			if(!autoflip_keep_going) autoflip_keep_going=true;	// if just switched off by PAGE_BACK switch it on
			else if(autofliptimerset) autoflip_enabled=false;	// if autoflip was really enabled off it otherwise just switch it on
		} else autoflip_enabled=autoflip_keep_going=true;		// enable autoflip
	}
	autoflip_update();
}

static void process_lock() {
/*	if(ivstate.keylock>0) autoflip_interface_on=false;
	else autoflip_interface_on=true;
 */
/*	if(stfile){
		if(ivstate.keylock>0) stfile->writeOperation(SFO_LockOn);
		else stfile->writeOperation(SFO_LockOff);
	}*/
}

static const struct {
	char *action;
	void (*f1)();
	void (*f2)();
	void (*f3)();
} KA[] = {
	{ "@KA_menu", open_quickmenu, NULL, NULL },
	{ "@KA_prev", prev_page, prev_page, NULL },
	{ "@KA_next", next_page, next_page, NULL },
	{ "@KA_pr10", jump_pr10, jump_pr10, stop_jump },
	{ "@KA_nx10", jump_nx10, jump_nx10, stop_jump },
	{ "@KA_goto", open_pageselector, NULL, NULL },
	{ "@KA_frst", first_page, NULL, NULL },
	{ "@KA_last", last_page, NULL, NULL },
	{ "@KA_prse", prev_section, NULL, NULL },
	{ "@KA_nxse", next_section, NULL, NULL },
	{ "@KA_obmk", open_bookmarks, NULL, NULL },
	{ "@KA_nbmk", new_bookmark, NULL, NULL },
	{ "@KA_nnot", new_note, NULL, NULL },
	{ "@KA_savp", save_page_note, NULL, NULL },
	{ "@KA_onot", open_notes, NULL, NULL },
	{ "@KA_olnk", open_links, NULL, NULL },
	{ "@KA_blnk", back_link, NULL, NULL },
	{ "@KA_cnts", open_contents, NULL, NULL },
	{ "@KA_srch", start_search, NULL, NULL },
	{ "@KA_dict", open_dictionary, NULL, NULL },
	{ "@KA_zmin", font_bigger, NULL, NULL },
	{ "@KA_zout", font_smaller, NULL, NULL },
	{ "@KA_hidp", show_hide_panel, NULL, NULL },
	{ "@KA_rtte", open_rotate, NULL, NULL },
	{ "@KA_mmnu", main_menu, NULL, NULL },
	{ "@KA_exit", exit_reader, NULL, NULL },
	{ "@KA_mp3o", open_mp3, NULL, NULL },
	{ "@KA_mp3p", mp3_pause, NULL, NULL },
	{ "@KA_volp", volume_up, NULL, NULL },
	{ "@KA_volm", volume_down, NULL, NULL },
	{ "@KA_conf", open_config, NULL, NULL },
	{ "@KA_abou", show_about, NULL, NULL },
	{ "@KA_mpdf", switch_autoflip, NULL, NULL },		// use to start/stop autoflip
	{ "@KA_lock", process_lock, NULL, NULL },		// need to check it to enable/disable autoflip
	{ NULL, NULL, NULL, NULL }
};

static void menu_handler(int pos) {

	char buf[32], *act;
	int i;

	if (pos < 0) return;
#if SDK_VERSION > 1
	// new style quick menu
	act = (char *)def_menuaction[pos];
#else
	sprintf(buf, "qmenu.fbreader.%i.action", pos);
	act = GetThemeString(buf, (char *)def_menuaction[pos]);
#endif

	reading_mode_on();

	for (i=0; KA[i].action != NULL; i++) {
		if (strcmp(act, KA[i].action) != 0) continue;
		if (KA[i].f1 != NULL) (KA[i].f1)();
		if (KA[i].f3 != NULL) (KA[i].f3)();
		break;
	}

}

static int act_on_press(char *a0, char *a1) {

	if (a0 == NULL || a1 == NULL || strcmp(a1, "@KA_none") == 0) return 1;
	if (strcmp(a0, "@KA_prev") == 0 || strcmp(a0, "@KA_next") == 0) {
		if (strcmp(a1, "@KA_pr10") == 0) return 1;
		if (strcmp(a1, "@KA_nx10") == 0) return 1;
	}
	return 0;

}

static int key_handler(int type, int par1, int par2) {

	char *act0, *act1, *act=NULL;
	int i;

	if (par1 >= 0x20) {

		// aplhanumeric keys
		return 0;

/*	} else if (par1 == KEY_BACK) {

		CloseApp();
		return 0;
*/
	} else if (par1 == KEY_POWER)  {

		// process lock if set
/*		if(type == EVT_KEYPRESS) {
			act = keyact0[par1];
			if(strcmp(act,"@KA_lock")==0)
				process_lock();
		}*/

		return 0;

	} else {

		act0 = keyact0[par1];
		act1 = keyact1[par1];
		if (type == EVT_KEYPRESS && act_on_press(act0, act1)) {
			act = act0;
		} else if (type == EVT_KEYRELEASE && par2 == 0 && !act_on_press(act0, act1)) {
			act = act0;
		} else if (type == EVT_KEYREPEAT) {
			act = act1;
		} else if (type == EVT_KEYRELEASE && par2 > 0) {
			act = act1;
			par2 = -1;
		}
		if (act == NULL) {
			// exit if back has no action mappings
			if ((par1 == KEY_BACK) && (act0 == NULL) && (act1 == NULL)) {
				exit_reader();
				return 0;
			}
			return 1;
		}

		for (i=0; KA[i].action != NULL; i++) {
			if (strcmp(act, KA[i].action) != 0) continue;
			if (par2 == -1) {
				if (KA[i].f3 != NULL) (KA[i].f3)();
			} else if (par2 <= 1) {
				if (KA[i].f1 != NULL) (KA[i].f1)();
				if (act == act0 && KA[i].f3 != NULL) (KA[i].f3)();
			} else {
				if (KA[i].f2 != NULL) (KA[i].f2)();
			}
			break;
		}
		return 1;

	}

}

static int pointer_handler(int type, int par1, int par2){

	int i;

	if (type == EVT_POINTERDOWN){
		x0=par1;
		y0=par2;

		Link=false;
		char *s;
		nlinks = 0;
		clink=0;

		s = get_backlink();

		if (s != NULL) {
			links[nlinks].x = 2;
			links[nlinks].y = 2;
			links[nlinks].w = 30;
			links[nlinks].h = 14;
			links[nlinks].kind = 37; // external link
			links[nlinks].href = strdup(s);
			nlinks++;
		}

		for (i=0; i<xxx_page_links.size(); i++) {
			xxx_link cl = xxx_page_links.at(i);
			links[nlinks].x = imgposx+cl.x1;
			links[nlinks].y = imgposy+cl.y1;
			links[nlinks].w = cl.x2-cl.x1+1;
			links[nlinks].h = cl.y2-cl.y1+1;
			links[nlinks].kind = cl.kind;
			links[nlinks].href = strdup(cl.id.c_str());
			nlinks++;
			if (nlinks >= MAXLINKS) break;
		}

		for (i=0; i<nlinks; i++)
			if (par1>=links[i].x && par1<=links[i].x+links[i].w && par2>=links[i].y && par2<=links[i].y+links[i].h){
				break;
			}
		if (i==nlinks){
			return 1;
		}

		Link=true;
		links_mode = 1;
		clink=i;
		invert_current_link();
		PartialUpdateBW(links[clink].x, links[clink].y, links[clink].w, links[clink].h);
		return 1;

	} else if (type == EVT_POINTERUP){
		if (Link){
			if (par1>=links[clink].x && par1<=links[clink].x+links[clink].w && par2>=links[clink].y && par2<=links[clink].y+links[clink].h){
				process_link(clink);
				links_mode = 0;
				Link=false;
				AddBkMark=false;
				return 1;
			} else {
				links_mode = 0;
				invert_current_link();
				PartialUpdateBW(links[clink].x, links[clink].y, links[clink].w, links[clink].h);
				Link=false;
				AddBkMark=false;
			}
		}
		if (AddBkMark){
			if (par1>ScreenWidth()-BOOKMARKMARGIN && par2<BOOKMARKMARGIN){
				for (i=0; KA[i].action != NULL; i++) {
					if (strcmp("@KA_nbmk", KA[i].action) != 0) continue;
					if (KA[i].f1 != NULL) (KA[i].f1)();
					break;
				}
			}
			AddBkMark=false;
			Link=false;
			return 1;
		}
		if (abs(x0-par1)<EPSX && abs(y0-par2)<EPSY){
			if (((x0>ScreenWidth()/2-MENUMARGIN && x0<ScreenWidth()/2+MENUMARGIN) ||
			      (par1>ScreenWidth()/2-MENUMARGIN && par1<ScreenWidth()/2+MENUMARGIN))
			      &&
			      ((y0>ScreenHeight()/2-MENUMARGIN && y0<ScreenHeight()/2+MENUMARGIN) ||
			      (par2>ScreenHeight()/2-MENUMARGIN && par2<ScreenHeight()/2+MENUMARGIN))){
				for (i=0; KA[i].action != NULL; i++) {
					if (strcmp("@KA_menu", KA[i].action) != 0) continue;
					if (KA[i].f1 != NULL) (KA[i].f1)();
					break;
				}
				return 1;
			}
			if ((x0>ScreenWidth()-BOOKMARKMARGIN || par1>ScreenWidth()-BOOKMARKMARGIN)
			      && (y0<BOOKMARKMARGIN || par2<BOOKMARKMARGIN)){
				for (i=0; KA[i].action != NULL; i++) {
					if (strcmp("@KA_obmk", KA[i].action) != 0) continue;
					if (KA[i].f1 != NULL) (KA[i].f1)();
					break;
				}
				return 1;
			}
		} else {
			if (x0<SCROLLPAGEMARGIN && y0>ScreenHeight()-SCROLLPAGEMARGIN && par1-x0>EPSX){
				for (i=0; KA[i].action != NULL; i++) {
					if (strcmp("@KA_prev", KA[i].action) != 0) continue;
					if (KA[i].f1 != NULL) (KA[i].f1)();
					break;
				}
				return 1;
			} else if (x0>ScreenWidth()-SCROLLPAGEMARGIN && y0>ScreenHeight()-SCROLLPAGEMARGIN && x0-par1>EPSX){
				for (i=0; KA[i].action != NULL; i++) {
					if (strcmp("@KA_next", KA[i].action) != 0) continue;
					if (KA[i].f1 != NULL) (KA[i].f1)();
					break;
				}
				return 1;
			} else if (x0>ScreenWidth()-FONTMARGIN && y0>BOOKMARKMARGIN && y0<ScreenHeight()-SCROLLPAGEMARGIN && y0-par2>EPSY){
				for (i=0; KA[i].action != NULL; i++) {
					if (strcmp("@KA_zmin", KA[i].action) != 0) continue;
					if (KA[i].f1 != NULL) (KA[i].f1)();
					break;
				}
				return 1;
			} else if (x0>ScreenWidth()-FONTMARGIN && y0>BOOKMARKMARGIN && y0<ScreenHeight()-SCROLLPAGEMARGIN && par2-y0>EPSY){
				for (i=0; KA[i].action != NULL; i++) {
					if (strcmp("@KA_zout", KA[i].action) != 0) continue;
					if (KA[i].f1 != NULL) (KA[i].f1)();
					break;
				}
				return 1;
			}
		}
		return 1;
	} else if (type == EVT_POINTERMOVE){

		return 1;
	} else if (type == EVT_POINTERHOLD){

		return 1;
	} else if (type == EVT_POINTERLONG){
		AddBkMark=false;
		if (par1>ScreenWidth()-BOOKMARKMARGIN && par2<BOOKMARKMARGIN){
			AddBkMark=true;
		}
		return 1;
	}
	return 0;
}

//orientation: 0=portrait, 1=landscape 90, 2=landscape 270, 3=portrait 180

bool lastturn=true;

static void rotate_timer()
{
	rotatetimerset=false;
	int cn = GetOrientation();
	int x,y,z,rotatepercent;
	bool turn=false;
	ReadGSensor(&x,&y,&z);

	if (rotate_angle<0)
		rotate_angle=0;
	else if (rotate_angle>10)
		rotate_angle=10;
	rotatepercent=angles_measured[rotate_angle];   //0=20РїС—Р…
	switch (cn) {
	case 0:turn=(abs(x)>rotatepercent); break;
	case 1:turn=(abs(y)>rotatepercent); break;
	case 2:turn=(abs(y)>rotatepercent); break;
	case 3:turn=(abs(x)>rotatepercent); break;
	}
	if (lastturn!=turn) {   // only one page turn at a time!
		lastturn=turn;
		if (turn) {
			if (rotate_mode>=5) {
				switch (cn) {
				case 0:turn=x<0; break;
				case 1:turn=y<0; break;
				case 2:turn=y>0; break;
				case 3:turn=x>0; break;
				}
				if (rotate_mode==6)   // back/forward reversed
					turn=!turn;
			}
			if (turn)
				next_page();
			else
				prev_page();
			SetAutoPowerOff(0);
			SetAutoPowerOff(1);  // reset auto-power-off timer!
		}
	}
	if (rotate_mode>3) {
		SetWeakTimer("RotatePage", rotate_timer, 200);
		rotatetimerset=true;
	}
}

// handler for autoflip page
static void autoflip_timer()
{
	autofliptimerset = false;
	// flip page
	next_page();
	// reset auto-power-off timer!
	SetAutoPowerOff(0);
	SetAutoPowerOff(1);
	// set timer again
	if (autopageflip>0) {
		SetHardTimer("AutoFlipPage", autoflip_timer, autopageflip*1000);
		autofliptimerset = true;
	}
}

// against compiler bugs
static void x_ready_sent(int n) { ready_sent = n; }

static int main_handler(int type, int par1, int par2) {
	fprintf(stderr,"main_handler: %d\n",type);

	ibitmap *bm;

	if (type == EVT_EXIT) {
		fprintf(stderr,"EVT_EXIT\n");
		save_state();
	}

	if (type == EVT_SHOW) {
		// set status panel
		if(hide_status_dialog_timeout==0) SetPanelType(1);
		else SetPanelType(0);

		repaint(soft_update ? 0 : 1);
		mainApplication->refreshWindow();
		soft_update = 0;
		x_ready_sent(ready_sent);
		if (! ready_sent) BookReady(OriginalName);
		ready_sent = 1;
		Link=false;
		AddBkMark=false;
		Long=false;
	}

	if (type == EVT_KEYPRESS || type == EVT_KEYREPEAT || type == EVT_KEYRELEASE) {
		return key_handler(type, par1, par2);
	}

	if (type == EVT_POINTERDOWN || type == EVT_POINTERUP || type == EVT_POINTERMOVE || type == EVT_POINTERHOLD || type == EVT_POINTERLONG){
		return pointer_handler(type, par1, par2);
	}

	if (type == EVT_PREVPAGE) {
		prev_page();
		return 0;
	}

	if (type == EVT_NEXTPAGE) {
		next_page();
		return 0;
	}

	if (type == EVT_OPENDIC) {
		open_dictionary();
		return 0;
	}

	if (type == EVT_ORIENTATION) {
		return ornevt_handler(par1);
	}

	if (type == EVT_SNAPSHOT) {
		fprintf(stderr, "EVT_SNAPSHOT\n");
		making_snapshot = 1;
		repaint(-1);
		PageSnapshot();
		making_snapshot = 0;
	}

	return 0;

}

static void sigsegv_handler(int signum) {

	x_ready_sent(ready_sent);
	if (! ready_sent) {
		fprintf(stderr, "SIGSEGV caught - clearing state\n");
		iv_unlink(DataFile);
		no_save_state = 1;
	} else {
		fprintf(stderr, "SIGSEGV caught\n");
	}
	exit(127);

}

//	ZLIntegerRangeOption &option = ZLTextStyleCollection::instance().baseStyle().FontSizeOption;

static int is_html(char *name) {

	char *p = strrchr(name, '.');
	if (! p) return 0;
	if (strcasecmp(p, ".html") == 0) return 1;
	if (strcasecmp(p, ".htm") == 0) return 1;
	if (strcasecmp(p, ".shtml") == 0) return 1;
	if (strcasecmp(p, ".asp") == 0) return 1;
	if (strcasecmp(p, ".php") == 0) return 1;
	if (strcasecmp(p, ".cgi") == 0) return 1;
	if (strcasecmp(p, ".jsp") == 0) return 1;
	return 0;

}

static int is_doc(char *name) {

	char *p = strrchr(name, '.');
	if (! p) return 0;
	return (strcasecmp(p, ".doc") == 0);

}

static int is_docx(char *name) {

	char *p = strrchr(name, '.');
	if (! p) return 0;
	return (strcasecmp(p, ".docx") == 0);

}

static void cleanup_temps() {

	system("rm -rf /tmp/fbreader.temp");

}

int main(int argc, char **argv) {

	FILE *f;
	char buf[1024];
	char *tbuf, *p, *pp;
	char *utfopt = "";
	int i, n;
	detectedlang[0]=0;

#ifndef EMULATOR
//	freopen("/mnt/ext1/fbreader.log", "a+", stderr);
//	fprintf(stderr, "starting FBReader...\n");
#endif

	// keep original panel state
	status_panel_old = (PanelHeight() == 0)?0:1;

	cleanup_temps();
	OpenScreen();

	// Measured values of G sensor for angles 20РїС—Р…-70РїС—Р… in 5РїС—Р… steps
	angles_measured[0]=19;
	angles_measured[1]=24;
	angles_measured[2]=29;
	angles_measured[3]=33;
	angles_measured[4]=38;
	angles_measured[5]=43;
	angles_measured[6]=47;
	angles_measured[7]=50;
	angles_measured[8]=52;
	angles_measured[9]=55;
	angles_measured[10]=57;
	gcfg = GetGlobalConfig();
	fcfg = OpenConfig(USERDATA"/config/fbreader.cfg", fbreader_ce);

	// get current interface language
	std::string sLang = ReadString(gcfg,"language","en");
	const int lang_length = sLang.length();
	for(i=0; i < lang_length; ++i)
	{
		sLang[i] = std::tolower(sLang[i]);
	}
	ZLibrary::OverrideLanguage(sLang);

	ko = ReadInt(gcfg, "keeporient", 0);
	if (GetGlobalOrientation() == -1 || ko == 0 || ko == 2) {
		orient = GetOrientation();
	} else {
		orient = ReadInt(fcfg, "orientation", GetOrientation());
		SetOrientation(orient);
	}

#if SDK_VERSION > 1
	m3x3 = const_cast<ibitmap*>(&fbreader_menu);
#else
	m3x3 = GetResource("fbreader_menu", NULL);
#endif
	if (m3x3 == NULL) m3x3 = NewBitmap(128, 128);
	bmk_flag = GetResource("bmk_flag", NULL);

	bgnd_p = GetResource("book_background_p", NULL);
	bgnd_l = GetResource("book_background_l", NULL);
	if (orient == 0 || orient == 3) {
		GetThemeRect("book.textarea", &textarea, 0, 0, ScreenWidth(), ScreenHeight(), 0);
	} else {
		GetThemeRect("book.textarea", &textarea, 0, 0, ScreenHeight(), ScreenWidth(), 0);
	}

#if SDK_VERSION > 1
	// new style quick menu
	for (i=0; i<9; i++) {
		strings3x3[i] = (char *)def_menutext[i];
	}
	//apply_localization2menu();
#else
	// old style quick menu
	for (i=0; i<9; i++) {
		sprintf(buf, "qmenu.fbreader.%i.text", i);
		strings3x3[i] = GetThemeString(buf, (char *)def_menutext[i]);
	}
#endif

/*	if (argc < 2) {
		Message(ICON_WARNING, "FBReader", "@Cant_open_file", 2000);
		return 0;
	}
*/
	char newestfile[255];
	if (argc==1) {
		// Open last-opened book!
		char* filename="/mnt/ext1/system/state/lastopen.txt";
		FILE* file=fopen(filename,"r");
		if (file) {
			fread(newestfile,1,254,file);
			newestfile[254]=0;
			fclose(file);
			p=strchr(newestfile,'\n');
			if (p)
				p[0]=0;
			//Message(ICON_WARNING, "FBReader", newestfile, 2000);
		}
		FileName=newestfile;
		if (newestfile[0]==0) {
			Message(ICON_WARNING, "FBReader", localize("Cant_open_file"), 2000);
			return 0;
		}
	} else
		FileName=argv[1];

	strcpy(autostr,"auto");
	if (detectedlang[0]) {
		strcat(autostr," (");
		strcat(autostr,detectedlang);
		strcat(autostr,")");
	}
	fcfg = OpenConfig(USERDATA"/config/fbreader.cfg", fbreader_ce);
	hyph_override = ReadInt(fcfg, "hyphenations", 1);
	rotate_mode = ReadInt(fcfg, "rotatemode", 0);
	rotate_angle = ReadInt(fcfg, "rotateangle", 2);
	invertoption = ReadInt(fcfg, "invertmode", 0);
	invertlimit = ReadInt(fcfg, "invertlimit", 0);
	hide_status_dialog_timeout = ReadInt(fcfg, "hidestatus_dialogtimeout", 0);

	hsdc_filename = ReadInt(fcfg, "hsdc_filename", 0);
	hsdc_title = ReadInt(fcfg, "hsdc_title", 1);
	hsdc_author = ReadInt(fcfg, "hsdc_author", 0);
	hsdc_series = ReadInt(fcfg, "hsdc_series", 0);
	hsdc_genres = ReadInt(fcfg, "hsdc_genres", 0);
	hsdc_separator = ReadInt(fcfg, "hsdc_separator", 0);
	hsdc_chapter = ReadInt(fcfg, "hsdc_chapter", 1);
	hsdc_pages = ReadInt(fcfg, "hsdc_pages", 1);
	hsdc_datetime = ReadInt(fcfg, "hsdc_datetime", 1);
	hsdc_battery = ReadInt(fcfg, "hsdc_battery", 1);

	// load autoflip settings
	int autoflip_index = ReadInt(fcfg, "autoflip", 0);
	if(autoflip_index>0) {
		autopageflip = atoi(autoflip_variants[autoflip_index]);
	}
	else autopageflip=0;
	fprintf(stderr,"auto page flip interval set to %d\n",autopageflip);

	// load list of available styles
	load_list_of_styles();

	bi = GetBookInfo(FileName);
	if (bi->title) book_title = strdup(bi->title);
	if (bi->author) book_author = strdup(bi->author);

	use_antialiasing = ivstate.antialiasing;

	DataFile = GetAssociatedFile(FileName, 0);
	ZetFile = GetAssociatedFile(FileName, 'z');
	f = iv_fopen(DataFile, "rb");
	if (f == NULL || iv_fread(&docstate, 1, sizeof(tdocstate), f) != sizeof(tdocstate) || docstate.magic != 0x9751) {
		docstate.magic = 0x9751;
		docstate.position = pack_position(0, 0, 0);
		strcpy(docstate.encoding, "auto");
		docstate.nbmk = 0;
	}
	if (f != NULL) iv_fclose(f);

	signal(SIGSEGV, sigsegv_handler);

	OriginalName = FileName;
	if (is_doc(FileName)) {
		system("mkdir " CONVTMP);
		if (strcasecmp(docstate.encoding, "auto") != 0 && strcasecmp(docstate.encoding, "utf-8") != 0) {
			utfopt = "-e";
		}
		sprintf(buf, "/ebrmain/bin/antiword.app -x db %s \"%s\" >%s", utfopt, FileName, CONVTMP "/index.html");
		fprintf(stderr, "%s\n", buf);
		system(buf);
		FileName = CONVTMP "/index.html";
	}
	if (is_docx(FileName)) {
		system("mkdir " CONVTMP);
		chdir(CONVTMP);
		sprintf(buf, "/ebrmain/bin/docx2html.app -o index.html -m index.files \"%s\"", FileName);
		system(buf);
		FileName = CONVTMP "/index.html";
	}

	if (strcmp(docstate.encoding, "auto") == 0 && is_html(FileName)) {
		f = iv_fopen(FileName, "rb");
		if (f != NULL) {
			tbuf = (char *) malloc(10001);
			n = iv_fread(tbuf, 1, 10000, f);
			tbuf[n] = 0;
			p = strstr(tbuf, "charset=");
			if (!p) p = strstr(tbuf, "CHARSET=");
			if (p) {
				p += 8;
				pp = strchr(p, '\"');
				if (pp != NULL && pp-p <= 12) {
					*pp = 0;
					for (pp=p; *pp; pp++) {
						if (*pp>='a' && *pp<='z') *pp -= 0x20;
					}
					strcpy(docstate.encoding, p);
				}
			}
			free(tbuf);
			iv_fclose(f);
		}
	}

	WriteString(fcfg, "encoding", docstate.encoding);
	encoding_override = docstate.encoding;

	WriteInt(fcfg, "preformatted", docstate.preformatted);
	break_override = docstate.preformatted;

	screenbuf = (unsigned char *)malloc(1024*1024/4);
	npages = 0;

	GetKeyMapping(keyact0, keyact1);
/*	for (i=0; i<32; i++) {
		if (keyact0[i]!=NULL && strcmp(keyact0[i],"@KA_mpdf")==0) keyact0[i]="@KA_none";
		if (keyact1[i]!=NULL && strcmp(keyact1[i],"@KA_mpdf")==0) keyact1[i]="@KA_none";
	}*/

#ifdef ENABLE_STATS_HARVESTING
	stats = new ZLPageStats();
	stfile = new ZLTextStatsFileWriter("/mnt/ext1/fbreader180.stats");
#endif

	printf("opening %s...\n", argv[1]);

	int zargc = 1;
	char **zargv = NULL;
	if (!ZLibrary::init(zargc, zargv)) {
		fprintf(stderr, "Zlibrary::init() failed\n");
		Message(ICON_WARNING, "FBReader", localize("Cant_open_file"), 2000);
		return 0;
	}

	ZLibrary::run(new FBReader(FileName));
	if (! book_open_ok) {
		fprintf(stderr, "Zlibrary::run() failed\n");
		Message(ICON_WARNING, "FBReader", localize("Cant_open_file"), 2000);
		return 0;
	}
	bookview = &(((FBReader *)mainApplication)->bookTextView());
	footview = &(((FBReader *)mainApplication)->footnoteView());

	apply_config(1, 0);

	if (argc >= 3) {
		jump_ref(argv[2]);
	} else {
		set_position(cpos = docstate.position);
	}
	printpos(": ", cpos);

	mainApplication->refreshWindow();

	InkViewMain(main_handler);

	reading_mode_off();

	cleanup_temps();

	// clear dynamic config entries
	if(styles_list) delete[](styles_list);
	styles_list = NULL;
	if(style_entries) delete[](style_entries);
	style_entries = NULL;

	// restore status panel
	SetPanelType(status_panel_old);

	if(stats) delete(stats);
	if(stfile) delete(stfile);

/*	free(screenbuf);
	if (TOC)
		free(TOC);*/
	return 0;

}
